Rust的核心功能之一是编译时强制引用的安全性,这是通过所有权机制和显式生命周期来实现的。是否可以实现可从中受益的“自定义”引用?
考虑以下示例。我们有一个表示图形的对象。假设我们可以通过引用它的边来遍历图,但是,这些引用是作为自定义索引实现的,而不是指向某个内存的指针。这样的索引可以简单地是一个数组(或三个)的偏移量,但它也可以是一个结合了一些标志等的结构。
除了遍历图形之外,我们还可以修改它,这意味着对其内部状态(边缘)的引用无效。理想情况下,我们希望编译器捕获任何这些无效引用。我们可以在Rust中这样做吗? E.g:
// get a reference to an edge
let edge = graph.get_random_edge()
// the next statement yields the ownership of the edge reference
// back to the graph, which can invalidate it
edge.split()
edge.next() // this will be a compile-time error as the edge is gone!
// another example
let edge1 = graph.get_random_edge()
let edge2 = graph.get_random_edge()
// this will be a compile-time error because the potentially invalid
// edge2 reference is still owned by the code and has not been
// yielded to the graph
edge1.split()
P.S。很抱歉没有提供信息的标题,我不知道该怎么说...
答案 0 :(得分:4)
是强>
完全有可能利用所有权和借入检查来建立自己的安全检查,这实际上是一个非常激动人心的探索领域,对我们开放。
我想从现有的酷事开始:
Sessions Types是关于在类型系统中编码状态机:
使用借用来为特定集合伪造有保证的有效索引(与品牌有关)有一些技巧:
让我们来看看你的例子:
// get a reference to an edge
let edge = graph.get_random_edge()
// the next statement yields the ownership of the edge reference
// back to the graph, which can invalidate it
edge.split()
edge.next() // this will be a compile-time error as the edge is gone!
这实际上是微不足道的。
在Rust中,您可以定义获取其接收者的所有权的方法:
impl Edge {
fn split(self) { ... }
// ^~~~ Look, no "&"
}
一旦消耗了该值,就不能再使用它,因此对next
的调用无效。
我认为您希望Edge
保留对图表的引用,以防止图形在您具有突出优势时被修改:
struct Edge<'a> {
graph: &'a Graph, // nobody modifies the graph while I live!
}
会做到这一点。
继续前进:
// another example
let edge1 = graph.get_random_edge()
let edge2 = graph.get_random_edge()
// this will be a compile-time error because the potentially invalid
// edge2 reference is still owned by the code and has not been
// yielded to the graph
edge1.split()
这是不可能的。
要强制执行订单,必须将值链接在一起,此处edge1
和edge2
不会。
一个简单的解决方案是要求edge1
充当图表的强制代理:
struct Edge<'a> {
graph: &'a mut Graph, // MY PRECIOUS!
// You'll only get that graph over my dead body!
}
然后,我们实现一个getter,暂时访问该图:
impl<'a> Edge<'a> {
fn get_graph<'me>(&'me mut edge) -> &'me mut Graph;
}
并使用该结果(为方便起见,命名为graph2
)来获取edge2
。
这创造了一系列义务:
graph
去世之前,没有人可以触摸edge1
edge1
去世之前,没有人可以触摸graph2
graph2
去世之前,没有人可以触摸edge2
强制以正确的顺序释放对象。
在编译时。
\ O /
安全注意事项:在Rust发布之后的一个重要事件是LeakPocalypse(scoped_thread
被发现不健全),这导致Gankro(谁编写并指导std::collections
)写{{ 3}}我鼓励你阅读。缺点是你永远不应该依赖于为了安全而执行的析构函数,因为它不能保证(对象可能被泄露然后线程因恐慌而解除)。 Pre-Pooping Your Pants是Gankro提出的解决这个问题的策略:将元素置于有效且安全(如果语义错误)状态,执行您的操作,恢复破坏时的真实语义,以及{ {1}}迭代器。
答案 1 :(得分:1)
您可以将生命周期添加到Edge
结构中,并借用Graph
方法中的get_random_edge
:
struct Graph;
impl Graph {
fn get_random_edge<'a>(&'a self) -> Edge<'a> {
Edge(self)
}
fn get_random_edge_mut<'a>(&'a mut self) -> MutEdge<'a> {
MutEdge(self)
}
}
struct MutEdge<'a>(&'a mut Graph);
impl<'a> MutEdge<'a> {
fn split(self) {}
fn next(&'a mut self) -> MutEdge<'a> {
MutEdge(self.0)
}
}
struct Edge<'a>(&'a Graph);
impl<'a> Edge<'a> {
fn split(self) {}
fn next(&'a self) -> Edge<'a> {
Edge(self.0)
}
}
这会产生以下错误:
37 | edge.split();
| ---- value moved here
38 | edge.next(); // this will be a compile-time error as the edge is gone!
| ^^^^ value used here after move
和
error[E0499]: cannot borrow `graph` as mutable more than once at a time
--> <anon>:43:17
|
42 | let edge1 = graph.get_random_edge_mut();
| ----- first mutable borrow occurs here
43 | let edge2 = graph.get_random_edge_mut();
| ^^^^^ second mutable borrow occurs here
如果您不想在边缘存储对{{1}}的引用,而只是存储索引,则只需将Graph
替换为&'a mut Graph
即可。 #39; t占用内存,但具有相同的语义。