是否可以将这里的类型参数T与约束size_of::<T>() == 4绑定,以便编译此函数(并且如果所提供的类型实际上不是大小为4,则foo的任何使用站点都会失败?

// I want this to compile
fn foo<T>(t: T) where T: Sized {
    unsafe { std::mem::transmute::<T, [u8; 4]>(t); };
}

// and this not to compile
fn bar() {
  let x = [0u8; 5];
  foo(x);
}

推荐答案

对于任何大小为非4的调用,您都可以使编译失败,但这不会使转换工作.您需要使用transmute_copy():

fn foo<T>(t: T) {
    struct AssertSize4<T>(T);
    impl<T> AssertSize4<T> {
        const ASSERT: () = if std::mem::size_of::<T>() != 4 {
            panic!("size is not 4 bytes");
        };
    }
    let _ = AssertSize4::<T>::ASSERT;
    let t = std::mem::ManuallyDrop::new(t);
    unsafe {
        std::mem::transmute_copy::<T, [u8; 4]>(&*t);
    };
}

请注意,这将生成post-monormphization错误,这意味着它将成功cargo check,但失败cargo build.

然而,您的代码仍然是不正确的:某个东西的大小是4个字节doesn't mean,它可以自由转换为4个字节.以下面的 struct 为例:

#[repr(C)]
struct Evil {
    _a: u8,
    _b: u16,
}

此 struct 的大小为4个字节,但将其传递给foo()将调用未定义的行为.这是因为它有填充字节,相当于未初始化的字节(单元化内存的另一个例子是未初始化的MaybeUninit).Transmuting uninitialized bytes to any type that does not allow uninitialized bytes, and that includes integers such as u8, is immediate Undefined Behavior.

一种可能性是将数据转化为[MaybeUninit<u8>; 4].但是,您可能永远不会认为这些字节是通过对它们执行诸如assume_init()之类的操作来初始化的.

另一个操作是确保字节被初始化.那么,如何只接受只具有初始化字节的类型呢?

你不能这么做.您唯一的机会就是创建一个unsafe trait OnlyInitialized,然后不安全地将其应用于类型.您可能还想为它提供一个派生宏(但判断是微妙的!不要假设你可以把它们都想起来).此时,只需使用bytemuck即可.

Rust相关问答推荐

捕获Rust因C++异常而产生panic

关联类型(类型参数)命名约定

创建包含缺失值的框架

如何在Tauri中将变量从后端传递到前端

在决定使用std::Sync::Mutex还是使用Tokio::Sync::Mutex时,操作系统线程调度是考虑因素吗?

无法从流中读取Redis请求

像这样的铁 rust 图案除了‘选项’之外,还有其他 Select 吗?

`*mut[T]`与`*mut T`的区别

程序在频道RX上挂起

borrow 是由于对 `std::sync::Mutex>` 的解引用强制而发生的

RUST 中的读写器锁定模式

max(ctz(x), ctz(y)) 有更快的算法吗?

没有得到无法返回引用局部变量`queues`的值返回引用当前函数拥有的数据的值的重复逻辑

在 Rust 中实现资源消耗的安全包装器

当 T 不是副本时,为什么取消引用 Box 不会抱怨移出共享引用?

如何在 use_effect_with_deps 中设置监听器内的状态?

RAII 模式的 Rust 解决方案,用于在 new() 和 drop() 上修改另一个对象

隐式类型闭包的错误生命周期推断

通用类型,不同于输入类型,作为函数的返回值

返回引用的返回函数