在实现两个段错误的类型上从特征 A 转换为特征 B

Transmuting from trait A to trait B on type that implements both segfaults

前几天有个OOP的问题想用downcast解决。作为一个自我挑战,我尝试使用 std::mem::transmute 和不安全的块来解决问题,这产生了分段错误。

这里是 full code in Rust Playground.

代码中有问题的部分是这样的:

unsafe {
    let storage =
        mem::transmute::<&mut Box<AnyStorable + 'static>, &mut Box<Insertable<C>>>(x);
    println!("{:#?}", x);
    storage.insert(component); // This segfaults
};

当 运行:

时产生段错误

/root/entrypoint.sh: line 7: 5 Segmentation fault timeout --signal=KILL ${timeout} "$@"

然而,当我替换这一行时:

let storage =
    mem::transmute::<&mut Box<AnyStorable + 'static>, &mut Box<Insertable<C>>>(x);

与:

let storage =
    mem::transmute::<&mut Box<AnyStorable + 'static>, &mut Box<VecStorage<C>>>(x);

有效。为什么第一行失败而第二行没有?

Box<SomeTrait> 存储了两个指针:一个指向对象,一个指向虚表。 Box<SomeType> 只存储一个指针:指向对象的指针。

您可以在示例中使用以下代码来查看尺寸:

println!("{}", mem::size_of::<Box<AnyStorable + 'static>>());
println!("{}", mem::size_of::<Box<Insertable<C>>>());
println!("{}", mem::size_of::<Box<VecStorage<C>>>());

调用transmute改变Box的特征会破坏虚表:不同特征的虚表不兼容。

调用 transmute 将对 Box<SomeTrait> 的引用更改为对 Box<SomeType> 的引用(并且类型恰好是正确的)恰好有效,因为它只会使用指向对象的第一个指针并忘记特征。

胖指针(即带有 vtable 的数据指针)的内部表示在 TraitObject 中定义,它只能在夜间构建中访问。尽管不太可能表示形式可能会改变,数据指针不再是第一个指针,这会破坏第二个 transmute.

TraitObject 的文档也值得一读。

(虽然 transmute 确保传递的类型的大小相等,但您传递的是对类型的引用 - 它总是恰好是一个指针大。它不检查这些引用指向的类型到。)