在 Rust 中,如何使用 `OnceLock` 创建全局共享单例?

问题描述 投票:0回答:1

假设我们需要定义一个具有线程安全的全局读访问权限的单例,规范的方法是使用

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 个错误似乎都毫无头绪,我怎样才能绕过这些错误并使静态绑定以最小的开销工作?

rust concurrency thread-safety static-variables
1个回答
0
投票

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.
© www.soinside.com 2019 - 2024. All rights reserved.