假设我有一个多态类型
以下是我的推理:
正如 Frxstrem 指出的那样,下面的代码确实会导致未定义的行为。我犯了一个愚蠢的错误:我忘记了为由 f 产生的 B 重新分配内存。看起来 上面的新代码通过了 Miri 检查。
T<A>
:#[repr(C)]
pub struct T<A> {
x: u32,
y: Box<A>,
}
以下是我的推理:
-
只要
T: Sized
,Box<T>
就保证会被表示为单个指针,并且与 C 指针(即 C 类型T*
)ABI 兼容。这意味着无论
A
是什么,y
应该具有相同的布局; 考虑到
T
上的#[repr(C)]
属性, 我预计对于所有的A
,T<A>
将共享相同的布局;因此,我应该能够原地修改
y
,甚至赋值不同类型的值Box<B>
。
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)
}
}
注意:
上述代码旨在进行多态更新,以便x
字段保持不变。假设x
不仅仅是一个u32
,而是一些非常大的数据块。整个想法是改变y
的类型(以及其值),而不影响字段x
。
正如 Frxstrem 指出的那样,下面的代码确实会导致未定义的行为。我犯了一个愚蠢的错误:我忘记了为由 f 产生的 B 重新分配内存。看起来 上面的新代码通过了 Miri 检查。
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)
}
}
A
的堆内存来创建新对象B
。新代码通过了 Miri 检查。但这并不意味着它是100%正确的,对吧? - Ruifeng Xief
发生恐慌,它会泄漏T
的分配。您在修改后的unsafe
代码中似乎也在其他地方做得很好。 - Aiden4