#生命周期 #所有权 # #ffi #no-std

no-std moveit

一个用于安全、就地构建 Rust(和 C++!)对象的库

7 个版本 (破坏性更新)

0.6.0 2023年6月15日
0.5.1 2022年8月25日
0.5.0 2022年3月28日
0.4.0 2022年1月26日
0.1.0 2021年4月13日

#198 in Rust 模式

Download history 14401/week @ 2024-04-20 13512/week @ 2024-04-27 15058/week @ 2024-05-04 18660/week @ 2024-05-11 13990/week @ 2024-05-18 11168/week @ 2024-05-25 19170/week @ 2024-06-01 16019/week @ 2024-06-08 8190/week @ 2024-06-15 3872/week @ 2024-06-22 2407/week @ 2024-06-29 2947/week @ 2024-07-06 2654/week @ 2024-07-13 2679/week @ 2024-07-20 2430/week @ 2024-07-27 2686/week @ 2024-08-03

10,973 每月下载量
35 个crate中使用了(直接使用4个)

Apache-2.0 OR MIT

87KB
1K SLoC

moveit

一个用于安全、就地构建 Rust(和 C++!)对象的库。

工作原理

moveit 围绕 unsafe trait 展开,这些 trait 对 !Unpin 类型施加额外的保证,使得它们可以在 C++ 的意义上进行移动。经常使用两种“移动”的含义

  • Rust 的意义,它类似于盲目的 memcpy,类似于 C++ 的 “std::is_trivially_moveable` 类型 trait。Rust 的移动也会使移动后的对象变得不可访问。
  • C++ 的意义,其中移动实际上类似于一个可变 Clone 操作,这使得移动后的值在作用域结束时仍然可访问,以便被销毁。

C++ 还有 构造函数,这是特殊的函数,可以在特定位置生成新的值。特别是,C++ 构造函数可能假设 *this 的地址不会改变;所有 C++ 对象都是有效地固定的,并且必须使用复制或移动构造函数来创建新对象。

New】,CopyNew,和 MoveNew trait 将这些概念引入 Rust。一个 [ New ] 类似于一个 nilary FnOnce,除了它不返回其结果,而是将其写入一个 Pin<&mut MaybeUninit<T>>,它处于 Pin 文档中描述的“内存可能被重新分配”状态(即它刚刚被分配或最近运行了析构函数)。这允许一个 [ New ] 依赖于指针地址保持稳定,就像 C++ 中的 *this 一样。

实现了 CopyNew 的类型可以进行 复制构造:给定指向 T: CopyNew 的任意指针,我们可以生成一个构造函数,在指定位置构造一个全新的、相同的 T。实现了 MoveNew 的类型可以进行 移动构造:给定指向 T拥有者 指针(参见 DerefMove),我们可以生成类似的构造函数,但它还会销毁 T 和拥有者指针的存储。

以上行为并不违反现有的 Pin 保证:从 Pin<P> 中移出并不执行 Rust 意义上的移动,而是在 C++ 意义上进行移动:通过被固定的指针以安全的方式修改,以构造一个新的 P::Target,然后销毁指针及其内容。

一般来说,可移动构造的类型将希望是 !Unpin,以便它们可以自我引用。自我引用类型是移动构造函数的主要动机之一。

构造函数

任何实现了 [New] 的类型都可以视为构造函数。构造函数类似于有保证的 RVO 的闭包,可以用于原地构造自我引用类型。以下是从 Pin<T> 文档中的例子

use std::marker::PhantomPinned;
use std::mem::MaybeUninit;
use std::pin::Pin;
use std::ptr;
use std::ptr::NonNull;

use moveit::new;
use moveit::new::New;
use moveit::moveit;

// This is a self-referential struct because the slice field points to the
// data field. We cannot inform the compiler about that with a normal
// reference, as this pattern cannot be described with the usual borrowing
// rules. Instead we use a raw pointer, though one which is known not to be
// null, as we know it's pointing at the string.
struct Unmovable {
  data: String,
  slice: NonNull<String>,
  _pin: PhantomPinned,
}

impl Unmovable {
  // Defer construction until the final location is known.
  fn new(data: String) -> impl New<Output = Self> {
    new::of(Unmovable {
      data,
      // We only create the pointer once the data is in place
      // otherwise it will have already moved before we even started.
      slice: NonNull::dangling(),
      _pin: PhantomPinned,
    }).with(|this| unsafe {
      let this = this.get_unchecked_mut();
      this.slice = NonNull::from(&this.data);
    })

    // It is also possible to use other `new::` helpers, such as
    // `new::by` and `new::by_raw`, to configure construction behavior.
  }
}

// The constructor can't be used directly, and needs to be emplaced.
moveit! {
  let unmoved = Unmovable::new("hello".to_string());
}
// The pointer should point to the correct location,
// so long as the struct hasn't moved.
// Meanwhile, we are free to move the pointer around.
let mut still_unmoved = unmoved;
assert_eq!(still_unmoved.slice, NonNull::from(&still_unmoved.data));

// Since our type doesn't implement Unpin, this will fail to compile:
// let mut new_unmoved = Unmovable::new("world".to_string());
// std::mem::swap(&mut *still_unmoved, &mut *new_unmoved);

// However, we can implement `MoveNew` to allow it to be "moved" again.

[new] 模块提供了各种辅助工具,用于制作构造函数。一般来说,在 Rust 中通常构建并返回值的函数应该返回 impl New。这与拥有 async fn.iter() 函数类似。

嵌入

上面的例子使用了 [moveit!()] 宏,这是将构造函数转换为值的方法之一。 moveit 为运行构造函数提供了两种选择

  • 在栈上,使用 MoveRef 类型(这就是 [moveit!()] 生成的)。
  • 在堆上,使用 Emplace 特性的扩展方法。

例如,我们可以通过编写 Box::emplace(Unmovable::new()) 将上述内容放入 Box 中。

许可证:Apache-2.0

这不是官方支持的产品。

依赖项

~185KB