在函数参数中解构包含借位的结构

时间:2017-01-26 08:46:54

标签: struct rust borrow-checker

我正在尝试实现一个使用借用检查/生命周期的系统,以便为集合提供安全的自定义索引。请考虑以下代码:

struct Graph(i32);

struct Edge<'a>(&'a Graph, i32);

impl Graph {
    pub fn get_edge(&self) -> Edge {
        Edge(&self, 0)
    }

    pub fn split(&mut self, Edge(_, edge_id): Edge) {
        self.0 = self.0 + edge_id;
    }

    pub fn join(&mut self, Edge(_, edge0_id): Edge, Edge(_, edge1_id): Edge) {
        self.0 = self.0 + edge0_id + edge1_id;
    }
}


fn main() {
    let mut graph = Graph(0);
    let edge = graph.get_edge();
    graph.split(edge)
}

当调用Edgesplit等方法时,应删除join结构借用的图表的引用。这将满足API不变量,即在图形变异时必须销毁所有边缘索引。但是,编译器无法获得它。它失败了

之类的消息
error[E0502]: cannot borrow `graph` as mutable because it is also borrowed as immutable
  --> src/main.rs:23:5
   |
22 |     let edge = graph.get_edge();
   |                ----- immutable borrow occurs here
23 |     graph.split(edge)
   |     ^^^^^ mutable borrow occurs here
24 | }
   | - immutable borrow ends here

如果我理解正确,编译器就无法意识到在调用函数时实际上正在释放边缘结构中发生的图形的借用。有没有办法教我编译器在这里做什么?

奖金问题:有没有办法完全相同但没有实际借用Edge结构中的图形?边缘结构仅用作遍历目的的临时结构,并且永远不会成为外部对象状态的一部分(我有这种边缘的弱版本)。

附录:经过一番挖掘后,似乎真的很琐碎。首先,Edge(_, edge_id)实际上并没有对Edge进行解构,因为_根本没有绑定(是的,i32是复制,这使得事情变得更加复杂,但是通过将其包装到非复制结构中可以很容易地解决这个问题。其次,即使我完全解构Edge(即通过在单独的范围内进行),对图形的引用仍然存在,即使它应该被移动(这必定是一个错误)。它只有在我在单独的函数中执行解构时才有效。现在,我知道如何规避它(通过一个单独的对象来描述状态变化并在提供索引时对其进行解构),但这很快变得非常尴尬。

1 个答案:

答案 0 :(得分:1)

还有第二个您没有提到的问题:split如何知道用户没有通过其他Edge来的Graph?幸运的是,可以用较高的特征范围解决这两个问题!

首先,让Edge带有PhantomData标记而不是对图形的真实引用:

pub struct Edge<'a>(PhantomData<&'a mut &'a ()>, i32);

第二,让我们将所有Graph操作移到新的GraphView对象中,该对象将通过使标识符无效的操作消耗

pub struct GraphView<'a> {
    graph: &'a mut Graph,
    marker: PhantomData<&'a mut &'a ()>,
}

impl<'a> GraphView<'a> {
    pub fn get_edge(&self) -> Edge<'a> {
        Edge(PhantomData, 0)
    }

    pub fn split(self, Edge(_, edge_id): Edge) {
        self.graph.0 = self.graph.0 + edge_id;
    }

    pub fn join(self, Edge(_, edge0_id): Edge, Edge(_, edge1_id): Edge) {
        self.graph.0 = self.graph.0 + edge0_id + edge1_id;
    }
}

现在我们要做的就是保护GraphView对象的构造,以使给定的生命周期参数'a不会超过一个。

我们可以通过(1)强制GraphView<'a>'aPhantomData成为GraphView成员,并且(2)仅提供构造impl Graph { pub fn with_view<Ret>(&mut self, f: impl for<'a> FnOnce(GraphView<'a>) -> Ret) -> Ret { f(GraphView { graph: self, marker: PhantomData, }) } } fn main() { let mut graph = Graph(0); graph.with_view(|view| { let edge = view.get_edge(); view.split(edge); }); } 到具有较高特质界限的闭包,每次都会创建新的生命周期:

pub fn view(&mut self) -> exists<'a> GraphView<'a>

invariant

这不是完全理想的,因为调用者可能必须经过扭曲才能将其所有操作放入闭包内。但是我认为这是我们在当前的Rust语言中可以做的最好的事情,它确实使我们能够执行大量的编译时保证,几乎没有其他语言可以表达。我希望看到以某种方式在语言中增加了对人体工程学的支持,这也许是一种通过返回值而不是闭包参数(if (window.localStorage && localStorage.getItem) { if (localStorage.getItem('token')) { //... } } history.push('/enable-browser-storage-page'); )来创建新生命周期的方法吗?