当已知两种类型具有完全相同的内存布局时,为什么 std::mem::transmute 不起作用?

Why does std::mem::transmute not work when both types are known to have the exact same memory layout?

提问人:Typhaon 提问时间:10/10/2023 最后编辑:Typhaon 更新时间:10/10/2023 访问量:91

问:

让我先说一下,我正在积极寻找其他选择,我只是惊讶于在这种情况下无法编译。transmutetransmute

我正在开发一个派生库,该库在编译时检查哪些字段已初始化。为此,我使用了 const 泛型。我掌握了基础知识,但我正在尝试添加一个功能,因此我正在查看.Buildertransmute

如果我使用 ,setter 的示例如下所示:transmute

#[derive(Builder)]
pub struct Foo<const SIZE: usize> {
    bar: [usize; SIZE],
}

// Just the setter part of the macro expansion:
impl<const SIZE: usize> FooBuilder<SIZE, false> {
    pub fn bar(mut self, bar: [usize; SIZE]) -> FooBuilder<SIZE, true> {
        self.data.bar = Some(bar);
        unsafe { std::mem::transmute(self) }
    }
}

但是 transmute 不是这样工作的。 完全相同,布尔值对底层数据没有任何影响,但我仍然得到:SIZE

cannot transmute between types of different sizes, or dependently-sized types
source type: `test::const_generic::FooBuilder<SIZE, false>` (size can vary because of [usize; SIZE])
target type: `test::const_generic::FooBuilder<SIZE, true>` (size can vary because of [usize; SIZE])

我只是好奇为什么会有这个限制。谁能向我解释一下?

// simplified macro expansion:
pub struct Foo<const SIZE: usize> {
    bar: [usize; SIZE],
}

impl <const SIZE: usize> Foo<SIZE> {
    fn builder() -> FooBuilder<SIZE, false> {
        FooBuilder::new()
    }
}

pub struct FooBuilder<const SIZE: usize, const __BUILDER_CONST: bool> {
    bar: Option<[usize; SIZE]>,
}

impl<const SIZE: usize> FooBuilder<SIZE, false> {
    pub fn new() -> FooBuilder<SIZE, false> {
        FooBuilder {
            bar: None,
        }
    }
}

impl<const SIZE: usize> FooBuilder<SIZE, false> {
    pub fn bar(mut self, bar: [usize; SIZE]) -> FooBuilder<SIZE, true> {
        self.bar = Some(bar);
        unsafe { std::mem::transmute(self) } 
              // ^ Should work, because the value of `__BUILDER_CONST` has no influence
              // on the layout of the struct, and `SIZE` is identical
    }
}

impl<const SIZE: usize> FooBuilder<SIZE, true> {
    pub fn build(self) -> Foo<SIZE> {
        Foo::<SIZE> {
            bar: self.bar.unwrap()
        }
    }
}

顺便说一句,实际的 setter 看起来更如下,我只是在添加新功能(用作 setter 输入类型)时遇到了一些奇怪的生命周期问题impl AsRef<T>

impl<const SIZE: usize> FooBuilder<SIZE, false> {
    pub fn bar(self, bar: [usize; SIZE]) -> FooBuilder<SIZE, true> {
        let mut data = self.data;
        data.bar = Some(bar);
        FooBuilder { data }
    }
}
Rust Casting 不安全的 const-generics 转换

评论

0赞 Caesar 10/10/2023
实际的 MRE 没有错误。无论如何:Rust 不保证任何关于结构布局的信息,如果我理解正确的话,甚至不保证 和 .它们实际上可能没有相同的大小。在这种情况下,Transmute 不起作用可能是对 Transmute 实现的任意不必要的限制。您可以使用 .请注意,transmute 可能对性能没有帮助。FooBuilder<_, false>FooBuilder<_, true>transmute_unchecked
0赞 Typhaon 10/10/2023
啊,谢谢,当我再次在电脑后面时,我会更新 MRE。我尝试的原因不是因为性能,而是因为如果我希望二传手接受(如果field_ty是 &'a str),我就会遇到终生问题。可悲的是,在每晚之外不可用,所以我仍在寻找解决方案。transmuteimpl AsRef<str> +'atransmute_unchecled
0赞 Caesar 10/10/2023
不使用或根本不使用怎么样?游乐场transmuteunsafe
1赞 Caesar 10/10/2023
对不起,我没有更彻底地阅读你的问题并立即告诉你:试图将自己从你不理解的终生错误中转化出来是一个糟糕的主意。我不完全确定,但我认为这是基于对一生的误解。我没有足够的信心来解释,但请考虑例如 是,所以你可以传入它,它会在 的末尾被丢弃。你可以解决这个问题,我认为人体工程学的缺点是不可避免的。无论如何,也许为生命周期问题发布一个单独的 SO 问题?String'staticbar&'a impl AsRef<str>
2赞 cafce25 10/10/2023
@Caesar或者只是接听并让来电者添加,或者我认为在这一点上,这更直接/方便。&'a str&.as_ref()

答:

1赞 Chayim Friedman 10/10/2023 #1

编译器中的分析限制性太强了。它不允许任何依赖于动态大小的转换,即使我们可以证明大小相等。

但是,请注意,在您的情况下,转换是不正确的:您需要添加到结构中。#[repr(transparent)]