6 个版本
0.0.8 | 2024 年 7 月 7 日 |
---|---|
0.0.7 | 2024 年 4 月 9 日 |
0.0.6 | 2023 年 4 月 8 日 |
0.0.5 | 2023 年 3 月 27 日 |
0.0.0 |
|
#159 in Rust 模式
505 每月下载量
用于 owned-pin
125KB
1K SLoC
Pinned-init
使用原地构造函数安全地初始化固定 struct
的库。
固定 是 Rust 确保数据不移动的方法。
它还允许原地初始化大型 struct
,否则会产生栈溢出。
此库的主要用例在 Rust-for-Linux 中。尽管此版本可以单独使用。
有些情况下,您希望原地初始化一个结构体。例如,当它非常大,无法将其从栈上移动,因为它的体积大于栈本身时。另一个原因可能是您需要对象的地址来初始化它。这与 Rust 的正常过程直接冲突,即首先初始化对象,然后将其移动到最终的内存位置。
此库允许您安全地进行原地初始化。
对于 alloc
和 std
功能需要夜间构建
当启用 alloc
或 std
功能时,此库需要不稳定的功能,因此只能与夜间编译器一起使用。内部使用的功能包括
allocator_api
new_uninit
get_mut_unchecked
当启用 alloc
或 std
功能时,用户将需要激活这些功能
allocator_api
概述
要使用原地构造函数初始化结构体,您需要以下两个条件
- 一个原地构造函数,
- 一个可以存储您的
struct
的内存位置(这可以是 栈、一个Arc<T>
、Box<T>
或任何实现了InPlaceInit
的智能指针)。
要获得就地构造函数,通常有三种选择
- 直接使用
pin_init!
宏创建就地构造函数, - 一个自定义函数/宏,由其他人提供就地构造函数,
- 使用不安全的函数 [
pin_init_from_closure()
] 手动创建初始化器。
除了固定初始化之外,此库还支持不带固定的就地构造,宏/类型/函数的命名通常与固定变体相同,但没有 pin
前缀。
示例
通过一些示例,我们将使用 CMutex
类型,该类型可在 ../examples/mutex.rs
中找到。它本质上是在用户空间中对 Linux 内核中的 mutex
的重建。因此,它也使用等待列表和基本的自旋锁。重要的是,它需要固定才能被锁定,因此是使用此库的理想选择。
使用 pin_init!
宏
如果您想使用 PinInit
,那么您将不得不使用 #[
pin_data
]
注解您的 struct
。这是一个使用 #[pin]
作为结构固定字段的标记的宏。完成此操作后,您可以通过 pin_init!
创建就地构造函数。语法几乎与常规 struct
初始化器相同。区别在于,对于您想要就地初始化的字段,您需要编写 <-
而不是 :
。
use pinned_init::*;
#[pin_data]
struct Foo {
#[pin]
a: CMutex<usize>,
b: u32,
}
let foo = pin_init!(Foo {
a <- CMutex::new(42),
b: 24,
});
foo
现在是 impl PinInit<Foo>
的类型。我们现在可以使用任何我们喜欢的智能指针(或者只是栈)来实际初始化一个 Foo
let foo: Result<Pin<Box<Foo>>, _> = Box::pin_init(foo);
有关更多信息,请参阅 pin_init!
宏。
使用返回初始化器的自定义函数/宏
许多使用此库的类型提供了一个返回初始化器的函数/宏,因为上述方法仅适用于可以访问字段类型的类型。
let mtx: Result<Pin<Arc<CMutex<usize>>>, _> = Arc::pin_init(CMutex::new(42));
要声明一个初始化宏/函数,您只需返回一个 impl PinInit<T, E>
#[pin_data]
struct DriverData {
#[pin]
status: CMutex<i32>,
buffer: Box<[u8; 1_000_000]>,
}
impl DriverData {
fn new() -> impl PinInit<Self, Error> {
try_pin_init!(Self {
status <- CMutex::new(0),
buffer: Box::init(pinned_init::zeroed())?,
}? Error)
}
}
手动创建初始化器
通常在与原始类型一起工作时,前面的方法并不足够。这时,[pin_init_from_closure()
] 就派上用场。这个 unsafe
函数允许您从闭包直接创建一个 impl PinInit<T, E>
。当然,您必须确保闭包确实以正确的方式进行了初始化。以下是需要注意的事项(我们称闭包的参数为 slot
)
- 当闭包返回
Ok(())
时,它已成功完成初始化,因此slot
现在包含类型T
的有效位模式。 - 当闭包返回
Err(e)
时,调用者可能需要释放slot
的内存,因此如果初始化过程中出现失败,您需要确保进行清理。 - 您可能假设即使在闭包返回后,
slot
也会保持固定,直到调用drop
slot
。
use pinned_init::*;
use core::{ptr::addr_of_mut, marker::PhantomPinned, cell::UnsafeCell, pin::Pin};
mod bindings {
extern "C" {
pub type foo;
pub fn init_foo(ptr: *mut foo);
pub fn destroy_foo(ptr: *mut foo);
#[must_use = "you must check the error return code"]
pub fn enable_foo(ptr: *mut foo, flags: u32) -> i32;
}
}
/// # Invariants
///
/// `foo` is always initialized
#[pin_data(PinnedDrop)]
pub struct RawFoo {
#[pin]
_p: PhantomPinned,
#[pin]
foo: UnsafeCell<bindings::foo>,
}
impl RawFoo {
pub fn new(flags: u32) -> impl PinInit<Self, i32> {
// SAFETY:
// - when the closure returns `Ok(())`, then it has successfully initialized and
// enabled `foo`,
// - when it returns `Err(e)`, then it has cleaned up before
unsafe {
pin_init_from_closure(move |slot: *mut Self| {
// `slot` contains uninit memory, avoid creating a reference.
let foo = addr_of_mut!((*slot).foo);
// Initialize the `foo`
bindings::init_foo(UnsafeCell::raw_get(foo));
// Try to enable it.
let err = bindings::enable_foo(UnsafeCell::raw_get(foo), flags);
if err != 0 {
// Enabling has failed, first clean up the foo and then return the error.
bindings::destroy_foo(UnsafeCell::raw_get(foo));
Err(err)
} else {
// All fields of `RawFoo` have been initialized, since `_p` is a ZST.
Ok(())
}
})
}
}
}
#[pinned_drop]
impl PinnedDrop for RawFoo {
fn drop(self: Pin<&mut Self>) {
// SAFETY: Since `foo` is initialized, destroying is safe.
unsafe { bindings::destroy_foo(self.foo.get()) };
}
}
有关如何使用 [pin_init_from_closure()
] 的更多信息,请查看 kernel
包内的用法。 sync
模块是一个好的起点。
依赖项
~88KB