Wow, do you happen to know how that first coercion happens? I have no clue how it manages to get the inner dyn Trait out of the ManuallyDrop via an as cast.
Ahhhhh duhhhhh I’m an idiot, it’s just a normal pointer cast
It doesn't really get anything out of anything. The pointer stays the same (as all pointer casts) and the metadata stays the same (the metadata for ManuallyDrop<dyn Trait> is still DynMetadata<dyn Trait>). It ends up being pretty much a transmute of the pointer (although mem::transmute is not guaranteed to work AFAIK).
In nightly land it would be the equivalent of the following:
fn func_1(x: *const ManuallyDrop<dyn SomeTrait>) -> *mut dyn SomeTrait {
let ptr = x as *const () as *mut ();
let metadata = core::ptr::metadata(x);
core::ptr::from_raw_parts_mut(ptr, metadata)
}
Or maybe it’s more accurate to say that the pointer cast is to some *const T such that T unsizes to dyn SomeTrait. And the compiler doesn’t know which to pick. (Or, from the error message, maybe it does try and it’s choosing wrong?)
There’s probably other similar possible sequences of coercions that I haven’t thought of yet.
I suggest you report a bug. Though if you ask me, there probably is a bug, but it's that the *const as *mut is accepted and not that *const as *const is not, as casting between different trait-object-unsized types is generally disallowed.
The reason for the bug is that the casts are handled differently in the compiler; we've had such bugs in the past.
I agree with @chrefr it should not be allowed. I wonder if it would be a breaking change to fix though.
#[allow(unused)]
fn main() {
trait SomeTrait { fn pr(&self); }
struct Foo<T: ?Sized>(u8, T);
impl SomeTrait for u64 {
fn pr(&self) { println!("{self}"); }
}
let a: Foo<u64> = Foo(0, 1);
let b = &a as &Foo<dyn SomeTrait>;
let c = b as *const Foo<dyn SomeTrait>;
println!("{:p}", c); // try comment out
let d = c as *mut dyn SomeTrait;
println!("{:p}", d); // try comment out
unsafe { (*d).pr(); }
}
My take is basically unsizing coercion is taking a strong priority for *mut to *mut or *const, and for *const to *const.
impl<T, U> CoerceUnsized<*const U> for *const T
where
T: Unsize<U> + ?Sized,
U: ?Sized,
impl<T, U> CoerceUnsized<*const U> for *mut T
where
T: Unsize<U> + ?Sized,
U: ?Sized,
impl<T, U> CoerceUnsized<*mut U> for *mut T
where
T: Unsize<U> + ?Sized,
U: ?Sized,
But there is no *const to *mut coercion (via unsizing or directly); that has to be a ptr-to-ptr cast.
I vaguely thought I'd seen this before but didn't find an issue. I agree it's worth it to file one for visibility if nothing else. (Edit: It's here.)
Discussed and FCP'd here,[1] though given the workarounds, I don't think any newly possible coercions were allowed by the PR per se (just an improvement on how you can do the coercions). So maybe the real FCP on metadata compatible casts is elsewhere.
you can cast between two wide raw pointers so long as the metadata is the same vtable in both", roughly speaking?
Yes. I consider this to be fixing an implementation bug, since we were previously preferring to attempt an "unsize" coercion even if it didn't work. This PR just fixes the pointer unsizing check to not commit to something that'll certainly cause an error, which will allow it to fall into the check for metadata compatibility. We already, for example, allow casting *const dyn Tr to *const W<dyn Tr>.
that PR was reverted, but for performance reasons ↩︎