#pin #move #owned #rvalue

owned-pin-macros

内存中同时拥有并固定数据的数据包装器

2个不稳定版本

0.2.0 2023年12月2日
0.1.0 2023年12月1日

#52#owned


用于 owned-pin

MIT/Apache

6KB
96

拥有固定Pin

Cargo Documentation License

此crate处理由某个实体拥有的数据,但(可能)在内存中不可移动。它受到C++中R值引用的启发。

有关更多信息,请参阅文档

示例

仅使用Pin<P>时,我们无法保证值的移动语义。

use core::pin::{Pin, pin};
use core::marker::PhantomPinned;

fn try_to_take_the_ownership_of<T>(pinned: Pin<&mut T>) {}

let mut value = pin!(PhantomPinned);
// The caller can reborrow the value...
try_to_take_the_ownership_of(value.as_mut());
// ... so the same pinned data can be used twice,
// thus unable to guarantee the move semantics.
try_to_take_the_ownership_of(value.as_mut());

实际上,这是因为没有这样的智能指针通过在堆栈上的某个位置持有唯一引用来拥有数据。

因此,我们引入了OnStack<T>智能指针和OPin<T>的别名 = Pin<OnStack<T>>,它们都在堆栈上“拥有”和“固定”数据,使上面的示例能够按预期工作

use owned_pin::{OPin, opin};
use core::marker::PhantomPinned;

fn take_the_ownership_of<T>(owned_and_pinned: OPin<T>) {}

let value = opin!(PhantomPinned);
// The `as_mut` method of `OPin` actually
// returns a `Pin<&mut T>`...
take_the_ownership_of(value);
// ... so the value itself cannot be used again.
// The line below causes rustc to emit `E0382`.
// take_the_ownership_of(value);

对于实现了Unpin的数据,我们甚至可以从包装器中安全地安全地将其移动出来

use owned_pin::{opin, unpin};

// Pins the value onto the stack.
let pinned = opin!(String::from("Hello!"));
// Retrieves back the data because `String` is `Unpin`.
let string: String = unpin(pinned);

动机:在Rust中使用C++的R值引用

中文版 (Chinese version)

此crate受到C++中R值引用的启发,并且OPin<T>的行为比Rust中原始的移动语义和Pin<&mut T>包装器更类似于R值引用。

Rust (原始) 与 C++

在Rust的原始移动语义中,当变量移动到另一个作用域时,其在堆栈上的存储位置会立即变为无效。

fn take_the_ownership_of(s: String) {
    let _ = s;
}

let a = String::from("Hello!");
take_the_ownership_of(a);

在上面的示例中,当a被移动到函数中时,a在原始调用堆栈上的原始存储位置(即堆指针的存储位置)、长度和容量在String的内部表示中立即变为无效和不可访问。

然而,C++中的移动语义将这种无效化延迟到原始值的移动引用(R值引用)被分配给新变量时

void take_the_ownership_of(std::string&&);

std::string a("Hello!");
// The original storage place of `a` keeps
// containing the valid value...
take_the_ownership_of(std::move(a));

void take_the_ownership_of(std::string&& s) {
    // ... until this actual assignment.
    std::string moved(std::forward<std::string>(s));
    // The usage of `s` before the assignment
    // doesn't invalidate the original storage
    // place.
}

在结论性的比较中,Rust的原始移动语义在没有优化的情况下,每次移动操作都会积极地将值复制到栈上,而C++版本将“语义上移动所有权”和“在栈上复制数据”的操作分开,理论上减少了后者的操作频率。

owned-pin 与 C++

这个crate的实现旨在模仿C++的移动语义

use owned_pin::{OPin, opin, unpin};

let a = String::from("Hello!");
// The original storage place of `a` keeps
// containing the valid value...
take_the_ownership_of(opin!(a));

fn take_the_ownership_of(s: OPin<String>) {
    // ... until this actual `unpin`ning.
    let moved = unpin(s);
    // The usage of `s` before the unpinning
    // doesn't invalidate the original storage
    // place.
}

这是通过OnStack<T>智能指针实现的,它是围绕&mut ManuallyDrop<T>的一个包装。当OnStack<T>静默地超出作用域时,它调用不安全的ManuallyDrop::drop函数来释放引用的T,而ManuallyDrop::take则在内值打算从引用中移动出去时调用。

OPin<T> 与 C++中的R值引用之间的简要比较

Rust中的OPin<T> C++中的T&&
letpinned= opin!(value); T&&rvalue= std::move(value);
letanother_pinned=pinned; T&&another_rvalue= std::forward<T>(rvalue);
letunpinned= unpin(another_pinned); Tassigned(std::forward<T>(another_rvalue));

最后一行假设Rust中的TUnpin,并且C++中T的移动构造函数没有被删除。

总的来说,这个crate使用户能够在Rust中采用C++的移动语义,这减少了栈复制的频率,同时保留了Rust编译时借用检查器和释放检查器的全部功能。

许可证

MIT OR Apache-2.0

依赖项

~320–780KB
~19K SLoC