-
Notifications
You must be signed in to change notification settings - Fork 13.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Ignore vtables in {Rc, Arc, Weak}::ptr_eq #80505
Conversation
r? @shepmaster (rust-highfive has picked a reviewer for you, use r? to override) |
Should we add tests for this to prevent regression? |
This is wrong, I am afraid -- it will also ignore the slice length when comparing |
@RalfJung I know that’s a problem for arbitrary pointer comparisons, but can that be a problem for |
Indeed, even unsafely converting |
Hm, that's an interesting argument -- However, making We'll have to see what the team thinks. |
It'd behave the same if the vtable issue was fixed, right? (That is, this change makes ptr_eq work correctly, but it'd behave differently than |
I'd think some new method could provide this, accompanied by the explanation of (a) why this works for reference counted pointers, and (b) how one should compare We've previously discussed providing |
I have not seen consensus for what the "correct" behavior of Note that vtables are not the only things that have "strange" equality -- the same is true for function pointers. |
The same argument applies to function pointers in We could make it clear that fat pointer casting quirks are not relevant here if we instead compared the addresses of the |
Another interesting exalple here is #48814 (comment) use std::{rc::Rc, mem};
struct A(u8);
#[repr(transparent)]
struct B(A);
trait T {
fn f(&self);
}
impl T for A {
fn f(&self) { println!("A") }
}
impl T for B {
fn f(&self) { println!("B") }
}
fn main() {
let b = Rc::new(B(A(92)));
let a = unsafe {
mem::transmute::<Rc<B>, Rc<A>>(b.clone())
};
let a: Rc<dyn T> = a;
let b: Rc<dyn T> = b;
println!("{}", Rc::ptr_eq(&a, &b));
} That said, I feel that the current behavior of |
If we’re considering changing the semantics of comparison of wide pointers to trait objects to ignore the vtable address, let’s do it uniformly including for the |
This behavior only seems correct for I do think some convenience method for this on |
r? @m-ou-se |
Review: this PR correctly implements the goal stated in its title and description. However that goal is flawed in the first place since it makes the modified methods inconsistent with |
@SimonSapin Your review doesn’t seem to acknowledge the above counterarguments:
|
I agree with RalfJung’s assessment
and with @burdges’s suggestion to make this a new method if the best-we-can-do-for-now ad-hoc fix for reference-counted things only is still wanted. |
I suppose
Could one instead just add some trick that deduplicates the vtables during codegen without doing |
This is exactly what LLVM |
If vtables cannot be inlined then linkonce should not have any downsides, right? |
We've discussed this issue in the library team meeting a few days ago. Those of use that attended the meeting agreed with @andersk: The documentation specifies "same allocation", and it makes sense for this function to effectively compare the address of the counters, as opposed to that of the value itself. (Just like owner-based comparison for shared_ptr/weak_ptr in C++.) That'd mean that the example in #80505 (comment) should return @SimonSapin @RalfJung What do you think? |
// The *const u8 cast discards the vtable to work around | ||
// https://github.com/rust-lang/rust/issues/46139 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is misleading, as this is not just because of that issue. If we're deciding now that ptr_eq
compares the allocation (like the documentation says), we will not change this back if that issue ever gets solved. This change will make the example in #80505 (comment) return true
on purpose. (Which would also make a good test case.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding was that we’re deeming that example an incorrect use of transmute
(as per #80505 (comment)), and thus making no guarantee whether it returns true
or false
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@m-ou-se the author needs help, after that we can move this ahead
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still disagree that transmuting |
@m-ou-se this is ready for review |
given that the team isn't in favour of this change in the current form, I'm going to close this PR. If you can find a better way to solve this, feel free to open a new PR and we can take this forward from there. Thanks for taking the time to contribute. |
I’m confused. What has changed since #80505 (comment)? This PR fixes a bug, and there seems to be agreement that it should be fixed. There’s an apparent controversy about whether we should document additional guarantees about the behavior under |
I'm also a bit confused that this was closed - as @andersk noted this is surely a bug fix (seemingly quite a serious one at that even)? As mentioned earlier the documentation for
Those are the semantics I've relied on in multiple projects but its only since I recently ran clippy on one of those projects that I discovered that I've shot myself in the foot here because that's not what Being able to reliably tell whether two Arcs point back to the same shared memory allocation is surely a really important/basic test that many projects might reasonably expect to trust |
|
ignoring the vtables doesn't seem like it's against the docs for Saying that the comparison is done in a "vein similar to ptr::eq" reads to me as only implying a superficial similarity, without setting any strict expectation about semantics. "vein similar" is very weak, non-assertive language. On the other hand saying that it "Returns true if the two Arcs point to the same allocation" seems like a very clear and precise description of what to expect. Personally I interpreted it as enough of a similarity that the two functions both deal with pointer comparisons, and would expect to read the docs for each separately if I wanted to check the specific semantics. |
Another consideration: Rust may have #![feature(trait_upcasting)]
use std::rc::Rc;
trait A {}
trait B: A {}
trait C: A {}
impl A for i32 {}
impl B for i32 {}
impl C for i32 {}
fn main() {
let x = Rc::new(1);
let y: Rc<dyn B> = x.clone();
let z: Rc<dyn C> = x.clone();
// This assertion is true in practice but is not currently guaranteed to be,
// and would be false sometimes if in a function that accepted `Rc<dyn A>`s as parameters.
assert!(Rc::ptr_eq(&(y as Rc<dyn A>), &(z as Rc<dyn A>)));
} [Edit, correction/clarification: It's also possible to do this in Rust today without trait upcasting, when one |
FWIW, based on the discussion above, the libs team seems to agree that comparing the pointer without its metadata is usually what one wants. The problem is that it is really surprising if Either way, a closed PR is also a bad place to discuss anything. I'd suggest opening a new issue for this, possibly a Zulip thread, and/or a libs-abi team ACP. |
ptr::eq: clarify that comparing dyn Trait is fragile Also remove the dyn trait example from `ptr::eq` since those tests are not actually guaranteed to pass due to how unstable vtable comparison is. Cc `@rust-lang/libs-api` Cc discussion following rust-lang#80505
ptr::eq: clarify that comparing dyn Trait is fragile Also remove the dyn trait example from `ptr::eq` since those tests are not actually guaranteed to pass due to how unstable vtable comparison is. Cc ``@rust-lang/libs-api`` Cc discussion following rust-lang#80505
ptr::eq: clarify that comparing dyn Trait is fragile Also remove the dyn trait example from `ptr::eq` since those tests are not actually guaranteed to pass due to how unstable vtable comparison is. Cc ``@rust-lang/libs-api`` Cc discussion following rust-lang/rust#80505
We sometimes generate duplicate vtables depending on the number of codegen units (#46139), so we need to exclude the vtable part when comparing fat pointers.
This is safe for the case of
Rc
,Arc
, andWeak
because these always point to an entire allocation. These methods can’t be used to compare, e.g., a struct with one of its fields, a slice with one of its subslices, or consecutive ZST fields.Fixes #63021.