Suppose I have a polymorphic type T<A>
:
#[repr(C)]
pub struct T<A> {
x: u32,
y: Box<A>,
}
Below are my reasonings:
According to the Memory Layout Section of
std::boxed
:So long as
T: Sized
, aBox<T>
is guaranteed to be represented as a single pointer and is also ABI-compatible with C pointers (i.e. the C typeT*
).So no matter what
A
is,y
should have the same layout;Considering additionally the
#[repr(C)]
attribute onT
, I expect that for allA
s,T<A>
would share the same layout;Thus I should be able to modify
y
in-place, even to values of a different typeBox<B>
.
My question is whether the following code is well-formed or has undefined behaviours? (The code below has been edited.)
fn update<A, B>(t: Box<T<A>>, f: impl FnOnce(A) -> B) -> Box<T<B>> {
unsafe {
let p = Box::into_raw(t);
let a = std::ptr::read(&(*p).y);
let q = p as *mut T<B>;
std::ptr::write(&mut (*q).y, Box::new(f(*a)));
Box::from_raw(q)
}
}
Note:
The above code aims to perform a polymorphic update in place, so that the x
field is left as-is. Imagine x
were not simply a u32
, but some very large chunk of data. The whole idea is to change the type of y
(along with its value) without touching field x
.
As is pointed out by Frxstrem, the code below indeed causes undefined behaviour. I made a silly mistake: I forgot to re-allocate memory for the B
produced by f
. It seems the new code above passes Miri check.
fn update<A, B>(t: Box<T<A>>, f: impl FnOnce(A) -> B) -> Box<T<B>> {
unsafe {
let mut u: Box<T<std::mem::MaybeUninit<B>>> = std::mem::transmute(t);
let a = std::ptr::read::<A>(u.y.as_ptr() as *const _);
u.y.as_mut_ptr().write(f(a));
std::mem::transmute(u)
}
}