4 个版本 (2 个重大更改)
0.3.0 | 2023 年 7 月 22 日 |
---|---|
0.2.1 | 2023 年 7 月 21 日 |
0.2.0 | 2023 年 7 月 20 日 |
0.1.0 | 2023 年 7 月 7 日 |
#625 in Rust 模式
69KB
980 行
genrc
此软件包提供对 std::sync::Arc
和 std::rc::Rc
的替代方案,它们是(几乎)直接替换,但允许子对象的引用计数指针,类似于 C++ 的 shared_ptr
。
主要功能,它增加了令人惊讶的灵活性:如果您有一个 Rc<T>
,并且 T
包含一些类型为 U
的子对象,则可以通过调用 Rc::project()
来构造一个与原始对象共享所有权的 Rc<U>
。
use genrc::rc::{Rc, Weak};
let a: Rc<[i32; 3]> = Rc::new([1, 2, 3]);
// convert the sized array into a slice
let b: Rc<[i32]> = Rc::project(a, |x| &x[..]);
// get a reference to one element of the array
let c: Rc<i32> = Rc::project(b, |x| &x[1]);
还有类型 RcBox<T>
(和 ArcBox<T>
)从 new_unique()
返回,它们利用新创建的引用计数指针仍然是唯一的这一事实,因此可以用于可变操作。
用途
更简单、更安全的初始化
您可以使用 RcBox<Option<T>>
来表示尚未初始化的类型,而不是使用 std::rc
中的各种不安全的 MaybeInit
相关 API。在对象初始化后,您可以使用 project
将其转换为普通的 Rc<T>
。
# use genrc::rc::{Rc, RcBox, Weak};
// construct the object initially uninitialized
let mut obj : RcBox<Option<i32>> = Rc::new_unique(None);
// ... later ...
// initialize the object
obj.replace(5);
// project to the inner value that we just created
let obj : Rc<i32> = RcBox::project(obj, |x| x.as_ref().unwrap());
assert_eq!(*obj, 5);
您还可以创建循环数据结构,而不需要使用 RefCell
或 new_cyclic
use genrc::rc::{Rc, RcBox, Weak};
struct Node {
edges: Vec<Weak<Node>>,
}
// Make a graph
let mut graph: Vec<RcBox<Node>> = (0..5).map(|_| {
Rc::new_unique(Node { edges: vec![] })
}).collect();
// Make some random edges in the graph
for i in 0..5 {
for d in 1..3 {
let j = (i + d) % 5;
let link = RcBox::downgrade(&graph[j]);
graph[i].edges.push(link);
}
}
// we still have unique handles on the nodes, so attempting to upgrade
// weak pointers will fail.
let p = graph[1].edges[0].clone();
assert!(p.upgrade().is_none());
// convert `RcBox` to a normal `Rc` with `into()`.
let graph: Vec<Rc<Node>> = graph.into_iter().map(Into::into).collect();
// now the weak pointers are valid - we've made a graph with (weak)
// cycles, no unsafe or internal mutation required.
assert!(Rc::ptr_eq(&graph[0].edges[1].upgrade().unwrap(), &graph[2]));
静态数据
与 std
不同,引用可以指向静态数据而不需要复制,这同样是通过使用 project()
实现的。
# use genrc::rc::Rc;
static BIGBUF: [u8; 1024] = [1; 1024];
let p: Rc<()> = Rc::new(());
let p: Rc<[u8]> = Rc::project(p, |_| &BIGBUF[..]);
assert!(std::ptr::eq(&BIGBUF[..], &*p));
因此,您可以使用 Rc
来跟踪可能拥有的、可能静态的数据,类似于 Cow
。
其他内容
夜间Rust allocator_api
支持
allocator_api
功能启用了不稳定分配器API,允许 Rc
和 Arc
使用自定义分配器。
Rc::new_in
返回一个类型为 Rc<T, A>
的 Rc
,其中分配器是类型的一部分。如果需要,您可以使用 Rc::erase_allocator()
来隐藏分配器。
生命周期
有些令人惊讶的是,std::rc::Rc
允许您创建一个指向局部变量的 Rc
。例如,这是合法的
use std::{cell::Cell, rc::Rc};
let x = Cell::new(1);
let y : Rc<&Cell<i32>> = Rc::new(&x);
x.set(2);
assert_eq!(y.get(), 2);
这种 Rc
的类型是 Rc<&'a T>
,其中 'a
是被引用者的生命周期,因此 Rc
不能比被引用者存活更长时间。
genrc::Rc
也可以这样做。但是,如果您使用 project()
将 Rc<&'a T>
转换为指向同一对象的 Rc<T>
,后者类型没有地方放置生命周期 'a
,因此如果允许这样,这将导致引用存活时间过长,成为一个鲁棒性错误。
为了避免这种情况,类型 Rcl<'a, T>
将生命周期参数添加到 Rc
中。
(实际上,Rc<T>
只是 Rcl<'static, T>
的别名,而 Arc<T>
是 Arcl<'static, T>
的别名。它们都是 genrc::Genrc
的别名,它是生命周期、引用类型、原子性、分配器和唯一性的泛型。)
要在一个短生命周期引用上使用 project()
,您必须使用 Rcl::project()
,它返回一个具有非静态生命周期的 Rcl
。
use genrc::rc::Rcl;
// Imagine we have some JSON data that we loaded from a file
// (or data allocated in an arena, etc)
let bigdata : Vec<u8> = b"Not really json, use your imagination".to_vec();
// buf points directly into `bigdata`, not a copy
let buf : Rcl<[u8]> = Rcl::from_ref(&bigdata[..]);
assert!(std::ptr::eq(&*buf, &bigdata[..]));
let word : Rcl<[u8]> = Rcl::project(buf, |x| &x[4..10]);
assert!(std::ptr::eq(&*word, &bigdata[4..10]));
由于生命周期通常是可以推断的,在大多数情况下 Rcl
与 Rc
的工作方式完全相同。主要的例外是在数据类型中,你可能需要显式指定生命周期。例如,如果你想要一个字段,它是一个 Rc<str>
类型,并且字符串可能存在生命周期较短的情况,你可以这样编写:
use genrc::rc::Rcl;
// Token in a parser where the buffer is an `Rc<str>`, and `text` can point
// directly into the buffer. (Or `text` can point to owned data, e.g. for
// unescaped strings, and callers generally don't have to care.)
struct Token<'a> {
some_data: u32,
text: Rcl<'a, str>
}
当类型擦除具有生命周期的自定义分配器时,也需要生命周期参数,因为 Rc<T>
也会隐藏分配器。
与 std::sync::Arc
和 std::rc::Rc
Rc::from_box
不会从原始盒子中复制对象。相反,它直接以当前的形式获取盒子的所有权,计数在单独的分配中。
如果你泄露了如此多的 Rc 对象,以至于引用计数溢出,std 指针将终止。但是 genrc
不会这样做,因为在 no_std
中没有 abort()
函数。
从 Rc<T>
到 Rc<dyn Trait>
的隐式转换不受支持,因为这需要一些不稳定的特征。但是你可以使用 Rc::project
显式进行转换。[待办:在夜间要求的功能后支持此功能.]
std 指针有各种与 MaybeUninit
相关的方法,用于分配后初始化对象。该 API 在 Genrc 中未提供,因为你可以使用 Option
和 project
在安全代码中完成相同的事情。
# use genrc::rc::{Rc, RcBox, Weak};
// construct the object uninitialized
let mut obj : RcBox<Option<i32>> = Rc::new_unique(None);
// ... later ...
// initialize the object
obj.replace(5);
// project to the inner value that we just created
let obj : Rc<i32> = RcBox::project(obj, |x| x.as_ref().unwrap());
assert_eq!(*obj, 5);
与 std 不同,Rc
和 Arc
(以及 RcBox
和 ArcBox
)共享单个泛型实现。Rc<T>
是别名 Genrc<'static, T, Nonatomic>
,而 Arc<T>
是别名 Genrc<'static, T, Atomic>
。这确实使文档看起来有些难看,因为它都在 Genrc
结构中,而不是你通常关心的实际类型。
std::rc::Rc::ptr_eq(a,b)
如果 a 和 b 共享相同的分配,则返回 true,这等同于询问它们是否是相等的指针。但在 genrc
中,这是两个不同的问题:你可以从同一个分配中获得指向两个不同子对象的指针,或者来自两个不同分配的指针指向同一个对象!(例如,它们可能被投影到一个静态对象)。因此,这里我们有 Rc::ptr_eq
,它相当于 std::ptr::eq(&*a, &*b)
,以及 Rc::root_ptr_eq
,后者检查计数是否共享。
from_raw
和 into_raw
不可用,因为返回的指针可能与原始分配没有关系。
与 shared-rc
的差异
shared-rc
与此非常相似的 crate;如果我知道 shared-rc 已经存在,我就不会写这个。话虽如此,还有一些差异
-
shared-rc
在底层使用Arc
和Rc
的 std 版本,因此它不支持零分配使用。 -
shared-rc
包含一个Owner
类型参数,具有显式的erase_owner
方法来隐藏它。genrc::arc::Arc
总是类型擦除所有者。这节省了指针中一个字节的开销,当类型擦除的shared-rc
指向一个无大小类型时。(例如,shared_rc::rc::[u8]
是 32 字节,但genrc::rc::[u8]
是 24。) -
genrc
在原子与共享之间是泛型的。shared-rc
使用宏来实现这一点,这使得 rustdocs 更难阅读,但“转到定义”更容易阅读。
与 rc-box
的差异
rc-box
crate 在 std Arc/Rc 周围添加了很好的 API:创建后立即知道你有了对其的唯一指针,所以将其放入一个实现 DerefMut
的包装类型中。这个 crate 复制了那个 API。
-
由于
rc-box
是基于 std 类型构建的,因此允许弱指针指向其RcBox
类型是不安全的,因此它不能像上面图例中的new_cyclic
一样替换。 -
在
genrc
中的实现是通用的,关于指针是否唯一(GenRc 的 UNIQ 参数)。这允许编写关于指针唯一性的通用代码,这在初始化时可能很有用(例如上面的图创建示例,其中图是一个Vec<RcBox<Node>>
,在初始化时,然后被转换为Vec<Rc<Node>>
。)
相关 Crates
shared-rc
:与这个 crate 类似,但它包装了 std 版本的Arc
和Rc
而不是重新实现它们。rc-box
:已知为唯一版本的 Rc 和 Arc。erasable
:擦除其具体类型的指针。rc-borrow
:Rc
和Arc
的借用形式。
待办事项
在一个功能后面实现各种 Unsize 特性。(尽管它们自 1.0 以来没有变化,但它们仍然是实现智能指针所必需的,尽管它们需要在夜间运行。)
如果计数溢出,使行为匹配 std
更丰富的自定义分配器 API。目前只提供了 new_in
和 from_box
;应该有 try_*
并支持 no_global_oom_handling
。
更多的文档示例。
许可证
genrc 根据 MIT 或 Apache 2.0 许可证进行许可,您选择哪一个。