I would like to remove Send from a DST, e.g. going from A<dyn Debug + Send> to A<dyn Debug> for arbitrary type A<T:?Sized> and have a couple questions:
Is there an instance when it would be unsound to use std::mem::transmute for this? I would assume the vtable layouts would alway be the same, but I suppose that may not be guaranteed.
Is there a nightly feature for doing this without unsafe? I know I can destructure A and then go from Box<dyn Debug + Send> to Box<dyn Debug> and reconstruct A, though that's a little round-about.
Is there a reason this is not already in stable given that the vtable pointer in A simply piggybacks (technical term) on the behavior of whatever pointer it contains (see example with Box below)?
This is a test to see what vtable is being used.
use std::fmt::Debug;
struct A<T:?Sized>(Box<T>);
fn vtable<T>(ptr: &T, desc: &str) {
let ptr = ptr as *const T as *const usize;
let vtable = unsafe { *ptr.offset(1) };
println!("{:<20} {:#x}", desc, vtable);
}
fn main() {
let b1: Box<dyn Debug + Send> = Box::new(2);
vtable(&b1, "Box Send:");
let b2: Box<dyn Debug> = Box::new(2);
vtable(&b2, "Box Unsend:");
let b3: Box<dyn Debug> = b1;
vtable(&b3, "Box Unsend Cast:");
let a1: A<dyn Debug + Send> = A(Box::new(2));
vtable(&a1, "A Send:");
let a2: A<dyn Debug> = A(Box::new(2));
vtable(&a2, "A Unsend:");
// Fails:
// let a3: A<dyn Debug> = a1;
// vtable(&a3, "A Unsend Cast:");
}
Returns something like:
Box Send: 0x5566ede88560
Box Unsend: 0x5566ede88560
Box Unsend Cast: 0x5566ede88560
A Send: 0x5566ede88560
A Unsend: 0x5566ede88560
Ah, yes, this does address the point about using a feature in nightly. Based on the docs I had assumed this would not work if my A has other non-phantom fields; i.e. I assumed
Such an impl can only be written if Foo<T> has only a single non-phantomdata field involving T.
means that Foo can only be a newtype essentially for T. I'm glad to learn that my reading was incorrect and coerce_unsized is much more flexible than that. In the example I provided, A is basically a newtype, but in my actual use-case it has other fields.
trait Fun {
type Assoc;
}
impl Fun for dyn Debug + Send {
type Assoc = usize;
}
impl Fun for dyn Debug {
type Assoc = &'static i32;
}
struct A<T: Fun>(T::Assoc);
// oops, null dereference
*transmute::<
A<dyn Debug + Send>,
A<dyn Debug>,
>(A(0)).0
Even if no associated types are involved, Rust could decide to lay out the two instantiations differently. Similarly, the fat pointer for dyn Debug + Send and dyn Debug could theoretically be ordered differently.
Technically even for casting Box<T> to Box<U> you're supposed to use from_raw(into_raw()) when T isn't a sized type. It's an annoying process, sure, but strictly speaking it is required. And slightly safer, even, since as casting between pointers will require they have compatible unsize kinds.
Because doing so automatically turns the choice of field types from a private implementation detail into a public API guarantee. And it's far from obvious that the current unstable magic functionality is the best way to opt in to such a guarantee.
Currently Copy and Drop are the only two stable implementable traits with language enforced limits on how you implement them. Extending the set of magic traits further is not a thing to be done lightly.
Good one! My situation does not use an associated type in A, but this is a nice counterexample to the general case showing that an as cast for custom types is not so simple. As an aside (I was not able to quickly find an answer): is the implementation impl Fun for dyn Debug limited to static dispatch or is there any way to use it as a trait object (i.e. dynamically dispatch on Fun methods)? Implementing a trait on a trait object is not something I've run into much.
This I don't fully follow, but I believe it's because you're talking about the general case where one wants to move between two unrelated unsize kinds (e.g. A<dyn Debug> to A<dyn Display>), and here using from_raw(into_raw()) is appropriate. I assume just removing + Send as in the following is fine: