#pointers #relative #ptr #smart

无std rel-ptr

构建可移动自引用类型的工具

9个版本

0.2.3 2019年4月12日
0.2.2 2019年3月17日
0.1.4 2019年3月12日

#120 in 无标准库

每月 31 次下载
用于 threadstack

MIT 许可证

41KB
535

rel-ptr

rel-ptr 是一个用于相对指针的库,可以用来创建可移动自引用类型。此库受到了Jonathan Blow在Jai上的工作的启发,他在Jai中添加了相对指针作为基本类型。

相对指针是一种使用偏移量和当前位置来计算其指向位置的指针。

最低Rust版本 = 1.34.0

安全性

有关安全信息,请参阅RelPtr类型文档

功能

no_std

此crate兼容no-std,只需添加功能no_std即可进入no_std模式。

nightly

使用nightly,您可以使用相对指针使用trait对象

示例

取下面的内存段

[.., 0x3a, 0x10, 0x02, 0xe4, 0x2b ..]

其中 0x3a 的地址是 0xff304050(32位系统),那么 0x2b 的地址是 0xff304054

如果我们有一个1字节相对指针 (RelPtr<_, i8>),地址为 0xff304052,那么这个相对指针也指向 0x2b,这是因为它的地址 0xff304052 加上它的偏移量 0x02 指向 0x2b

这里有几点有趣的事情

  1. 只需1个字节即可指向另一个值,
  2. 相对指针不能访问所有内存,只能访问附近的内存
  3. 如果相对指针和被指向的对象一起移动,那么相对指针不会被无效化

第三点使得可移动自引用结构体成为可能

类型 RelPtr<T, I> 是一个相对指针。 T 表示它所指向的内容,而 I 表示它用来存储偏移量。在实际应用中,你可以忽略 I,因为它默认为 isize,这将覆盖你使用相对指针的所有情况。但是,如果你想优化指针的大小,你可以使用任何实现了 Delta 的类型。一些来自 std 的实现类型包括:i8i16i32i64i128isize。请注意,权衡是,随着你减小偏移量的大小,你指向的范围也会减小。 isize 至少覆盖了一半的地址空间,所以除非你做了什么非常疯狂的事情,它应该可以工作。对于自引用的结构体,使用一个最大值至少与你的结构体一样大的类型。即 std::mem::size_of::<T>() <= I::max_value()

关于 usized 类型的说明:这些类型更难以实现

自引用类型示例

 struct SelfRef {
     value: (String, u32),
     ptr: RelPtr<String, i8>
 }

 impl SelfRef {
     pub fn new(s: String, i: u32) -> Self {
         let mut this = Self {
             value: (s, i),
             ptr: RelPtr::null()
         };
         
         this.ptr.set(&mut this.value.0).unwrap();
         
         this
     }

     pub fn fst(&self) -> &str {
         unsafe { self.ptr.as_ref_unchecked() }
     }

     pub fn snd(&self) -> u32 {
         self.value.1
     }
 }

 let s = SelfRef::new("Hello World".into(), 10);
 
 assert_eq!(s.fst(), "Hello World");
 assert_eq!(s.snd(), 10);
 
 let s = Box::new(s); // force a move, note: relative pointers even work on the heap
 
 assert_eq!(s.fst(), "Hello World");
 assert_eq!(s.snd(), 10);

这个示例是人为构造的,仅作为示例。在这个示例中,我们可以看到一些重要的安全可移动自引用类型的部分,让我们逐个分析。

首先,SelfRef 的定义,它包含一个值和一个相对指针,相对指针将指向 SelfRef.value 中的元组内部,指向 String。没有生命周期涉及,因为它们要么会使 SelfRef 不可移动,要么无法正确解析。

SelfRef::new 中我们看到一个模式,首先创建对象,并使用哨兵 RelPtr::null(),然后立即使用 RelPtr::set 分配值并解包结果。这个解包操作可以快速得到反馈,以确定指针是否已设置,如果没有设置,则可以增加偏移量的大小并解决它。

一旦指针设置,移动结构体仍然是安全的,因为它使用的是 相对 指针,所以它所在的位置无关紧要,只有它相对于其指针的目标的偏移量。在 SelfRef::fst 中,我们使用 RelPtr::as_ref_unchecked,因为它不可能使指针无效。这是不可能的,因为我们不能直接设置相对指针,也不能在设置相对指针后更改 SelfRef 字段偏移量。


发行说明

0.2.3

变更

  • 由于 rustc 版本 1.34.0,已将 NonZero* 移出夜间版本

0.2.2

移除

  • 移除了对 unreachable 的依赖
    • 在存在 std::hint::unreachable 的情况下是不必要的,并且使用 std 版本更安全,因为 std 版本保证会被优化掉
    • 在调试模式下表现与预期不同,新实现会在调试模式下崩溃,并在发布模式下被优化掉

0.2.1

新增功能

  • 关于 Nullable 的文档以及它与 Delta 的交互

变更

  • 修复了可变性错误,获取原始指针(*mut T)或非可空指针(NonNull<T>)时,应在 RelPtr 上获取独占锁

0.2.0

新增功能

  • TraitObject 上添加了构造函数,现在有 from_reffrom_mut 以便更容易地在 TraitObject 之间进行转换
  • 更多文档

移除

  • Default 对于 MetaData::Data 的限制
    • 现在在设置相对指针之前访问 MetaData::Data 是未定义行为的
  • 建议使用新的构造函数而不是 TraitObject::new

变更

  • 重构了 MetaData::decompose
    • 更改为 MetaData::data,可以通过指针转换提取指针,因此只需要数据
  • MetaData 转换为使用 std::mem::NonNull,因为它更容易处理
    • 这是由于使用 Option<NonNull<T>> 允许在 T: !Sized 的情况下表示空值

注释

我不预期 API 将有任何大规模的变化,因此这应该是最终的 API。在发布 1.0.0 之前,我将等待并查看是否有任何错误。我还需要等待关于类型布局的这次 GitHub 讨论的结果,因为它与移动自引用类型的安全性模型有关。

0.1.4

新增功能

  • NonZero* 整数的支持
  • 格式化支持格式化的所有 RelPtr

变更

  • 将 API 转换为使用 &mut T 而不是 &T

    • 这更好地表示了 RelPtr 的语义,并由 Yandros 建议
  • Delta::ZERO 移动到 Nullable::NULL

    • 这是为了支持 NonZero* 类型
  • 更新文档以更好地解释可能的未定义行为

  • TraitObject::into 更改为 TraitObject::as_refTraitObject::as_mut

无运行时依赖

功能