I'm trying to write a function to determine whether a Box<T> and *const U point to the same thing, but in a context where T & U: ?Sized (psuedo-code). All I care about is the pointed-to-addresses being equal or not.
Reading online it seems like using std::ptr::eq is a no go, because for fat pointers it checks both the data pointer and vtable pointer for equality, and vtable addresses aren't stable (see https://github.com/rust-lang/rust/issues/69757).
But, all that matters is whether the data addresses match, I don't care about the vtable or the type of the pointer. So I was wondering if anyone could help clear up 2 things for me : v)
Is it always safe to transmute a fat pointer to a (usize, usize), and will they always be in the order of (data, vtable) when dealing with DSTs? (and outside of const contexts).
Is there a better way to perform such a check without transmuting? All of this sounds pretty reasonable in my head but so have lots of other bad ideas! Somewhere along this rabbit hole someone used something special like *const () to thin-ify the pointer and discard it's typing or something, but I can't seem to find where I saw that, and trying to search "const ()" is... difficult...
Anyways! Thanks for any help, and feel free to lambaste any of the insanities my question is predicated on!!
Just do a cast pointer as *const () (or pointer.cast::<()>() should do the same) for both pointers, and compare the resulting pointers. It doesn't need to be a *const () specifically, casting e g. as *const u8 works just as well. Really, for comparing the addresses the type does not matter very much.
(Using transmute the way you described would likely result in undefined behavior, or at least in relying on unstable layout of types, so compilation could become UB or could at least break with future compiler versions.)
The layout of fat pointers is unspecified, so relying upon the representation of DST pointers results in undefined behavior. as mentioned previously, casting to *const () is fine.
If you really want to go the "screw sane approaches I want to rely upon unstable compiler/standard library implementation details" route, trait object pointers are currently stored as this:
Well now I just feel dumb
I thought there was some special syntactical magic around *const (), I didn't realize it was literally just the unit type!
or at least in relying on unstable layout of types
I figured the transmute was the wrong way to go, but was curious about whether the layout of DST pointers was considered 'stable' or just an implementation detail. Good to know it's the former!
Anyways, thanks for the information and recommendation!! I guess I'll go with the *const () even though like you say it shouldn't really matter. Hopefully it'll better signal that the types are completely irrelevant here.
trait object pointers are currently stored as this
You know, I actually did stumble across that! But thankfully for the other people stuck with me, we're using stable, and it's a nightly-only API (for good reason)!
Thanks for confirming the instability of the layout, and also pointing me in the direction of sanity: *const ().
Given your description, your reply may be "I know and I don't care", but I'll throw this out there anyway in case.
When you're comparing addresses, especially across types and when dealing with DSTs (wide pointers), you may get false positives (as in "same address but not the same thing") from situations like
Compared a struct to the leading field (transitively)
Thanks, I'll always take any additional information I can get!
Buttttt, I'm afraid you already guessed my response
The actual type bound is T: Element + ?Sized, where Element is an internal trait implemented by a handful of structs.
None of these structs are zero-sized, contain slices, or one another (so no leading field concern). So they should skirt the edge concerns you list, but if there's one I've overlooked; like I said, I'll always appreciate information!