假设我们需要定义一个具有线程安全的全局读访问权限的单例,规范的方法是使用
OnceLock
:
/// A synchronization primitive which can be written to only once.
///
/// This type is a thread-safe [`OnceCell`], and can be used in statics.
static i1: OnceLock<Box<u64>> = OnceLock::new(); // this works
static i2: OnceLock<Box<dyn Sized>> = OnceLock::new(); // this doesnt:
第二行抛出几个没有意义的错误:
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; for more information visit <https://doc.rust-lang.org/reference/items/traits.html#object-safety>
Sized 是一个具有动态大小的特征,但 Box 只是一个智能引用,指向稍后动态分配的结构体,并且在这个定义中它甚至没有初始化,所以为什么我需要将它变成一个对象?
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”的文档中字面意思,怎么会被认为是不安全的呢?
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`
任何静态绑定在内存中只有 1 个副本,并且被进程中的所有线程使用,不需要像某些分布式计算框架那样在线程之间发送它们。编译器怎么可能没有意识到这一点?
所以这 3 个错误似乎都毫无头绪,我怎样才能绕过这些错误并使静态绑定以最小的开销工作?
dyn Sized
自相矛盾,Sized
意味着,一种类型的所有项目都具有静态已知的大小,但是Sized
是针对多种类型实现的,()
,u8
,u16
,...都带有不同的大小,这意味着 dyn Sized
的大小可以为 0 字节、1 字节、2 字节……,并且您可以在运行时更改底层类型,因此它是 not 静态已知的。
这意味着 Trait 对象(因为它可以是其 Trait 所实现的任何类型)无法实现 Sized
,但实现指定的 Trait 就是我们所知道的 Trait 对象所做的一切。换句话说,dyn Sized
不实现Sized
,但必须同时实现Sized
。
第二个和第三个错误归结为
statics
需要是 Sync
(它们可以从任何线程访问,并且 Sync
是告诉编译器这样做是安全的特征),但是 OnceCell
只实现 Sync
如果它的内容是 Sync + Send
一个特征对象,但仅实现它列出的特征,所以 dyn Trait
既不实现 Send
也不实现 Sync
。
OnceCell
的来源解释:
// Why do we need `T: Send`? // Thread A creates a `OnceLock` and shares it with // scoped thread B, which fills the cell, which is // then destroyed by A. That is, destructor observes // a sent value.