Rust中用OnceLock创建全局单例:Box<dyn Sized>报错解决
我们需要定义一个支持全局读取的线程安全单例,标准方法是使用OnceLock:
/// 一种仅能写入一次的同步原语。 /// /// 该类型是线程安全的[OnceCell],可用于静态变量。 static i1: OnceLock<Box<u64>> = OnceLock::new(); // 此代码正常运行 static i2: OnceLock<Box<dyn Sized>> = OnceLock::new(); // 此代码报错
第二行代码抛出三个错误:
错误1:Sized无法被转为对象
error[E0038]: the trait `Sized` cannot be made into an object --> src/lib.rs:118:25 | 118 | static i2: OnceLock<Box<dyn Sized>> = OnceLock::new(); | ^^^^^^^^^ `Sized` cannot be made into an object | = note: the trait cannot be made into an object because it requires `Self: Sized` = note: for a trait to be "object safe" it needs to allow building a vtable to allow the call to be resolvable dynamically
疑问:Sized是标记固定大小类型的 trait,Box只是指向动态分配内存的智能引用,且此处未初始化,为何需要将其转为对象?
错误2:无法在线程间安全共享
error[E0277]: `(dyn Sized + 'static)` cannot be shared between threads safely --> src/lib.rs:118:12 | 118 | static i2: OnceLock<Box<dyn Sized>> = OnceLock::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Sized + 'static)` cannot be shared between threads safely | = help: the trait `Sync` is not implemented for `(dyn Sized + 'static)` = note: required for `Unique<(dyn Sized + 'static)>` to implement `Sync` note: required because it appears within the type `Box<(dyn Sized + 'static)>` --> /home/peng/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:195:12 | 195 | pub struct Box< | ^^^ = note: required for `OnceLock<Box<(dyn Sized + 'static)>>` to implement `Sync` = note: shared static variables must have a type that implements `Sync`
疑问:OnceLock文档明确说明它是线程安全的、可用于静态变量,为何会被判定为不安全?
错误3:无法在线程间安全传递
error[E0277]: `(dyn Sized + 'static)` cannot be sent between threads safely --> src/lib.rs:118:12 | 118 | static i2: OnceLock<Box<dyn Sized>> = OnceLock::new(); | ^^^^^^^^^^^^^^^^^^^^^^^^ `(dyn Sized + 'static)` cannot be sent between threads safely | = help: the trait `Send` is not implemented for `(dyn Sized + 'static)` = note: required for `Unique<(dyn Sized + 'static)>` to implement `Send` note: required because it appears within the type `Box<(dyn Sized + 'static)>` --> /home/peng/.rustup/toolchains/stable-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/alloc/src/boxed.rs:195:12 | 195 | pub struct Box< | ^^^ = note: required for `OnceLock<Box<(dyn Sized + 'static)>>` to implement `Sync` = note: shared static variables must have a type that implements `Sync`
疑问:静态变量在内存中只有一个副本,无需在线程间传递,编译器为何会报错?
Sized无法转为对象的本质:Sizedtrait的定义自带Self: Sized约束,意味着它只能被有固定大小的类型实现,而trait object(即dyn Trait形式的类型)本身是动态大小类型(DST),不满足Sized约束,因此dyn Sized从根本上就是矛盾的,无法被实例化或作为Box的内部类型。线程安全相关错误的根源:
静态变量要求其类型必须实现Synctrait(确保多线程读安全)。OnceLock<T>实现Sync的前提是T实现Sync,而Box<T>实现Sync的前提是T实现Sync。由于dyn Sized既没有自动实现Send也没有实现Sync,导致整个类型链不满足静态变量的线程安全要求。另外,即使静态变量不需要被“传递”,但
Sync的核心是保证多线程同时访问时的内存安全,编译器只会根据类型的trait约束做判断,不会考虑实际使用场景。
如果你的需求是存储任意固定大小的'static类型,可以用dyn Any替代dyn Sized,Any是对象安全的trait,且所有Sized + 'static类型都自动实现Any,同时dyn Any默认实现Send + Sync(当内部类型满足时)。修改后的代码如下:
use std::sync::OnceLock; use std::any::Any; static i2: OnceLock<Box<dyn Any + Send + Sync>> = OnceLock::new();
如果不需要动态类型转换,直接指定具体类型(如i1的写法)是开销最小的方案。如果需要更灵活的泛型单例,可以结合宏或封装结构体实现,但静态变量无法直接使用泛型。
内容的提问来源于stack exchange,提问作者tribbloid

