解构在函数参数中包含借位的结构 [英] Destructuring a struct containing a borrow in a function argument

查看:71
本文介绍了解构在函数参数中包含借位的结构的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I am trying to implement a system that would use borrow checking/lifetimes in order to provide safe custom indices on a collection. Consider the following code:

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)
}

当调用 splitjoin 等方法时,应该删除对 Edge 结构借用的图的引用.这将满足 API 不变性,即在图发生变异时必须销毁所有边索引.但是,编译器没有得到它.它失败了像

References to the graph borrowed by the Edge struct should be dropped when methods such as split or join are called. This would fulfill the API invariant that all edge indices must be destroyed when the graph is mutated. However, the compiler doesn't get it. It fails with messages like

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

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

If I understand this correctly, the compiler fails to realise that the borrowing of the graph that happened in the edge struct is actually being released when the function is called. Is there a way to teach the compiler what I am trying to do here?

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

Bonus question: is there a way to do exactly the same but without actually borrowing the graph in the Edge struct? The edge struct is only used as a temporary for the purpose of traversal and will never be part of an external object state (I have 'weak' versions of the edge for that).

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

Addendum: After some digging around, it seems to be really far from trivial. First of all, Edge(_, edge_id) does not actually destructure the Edge, because _ does not get bound at all (yes, i32 is Copy which makes things even more complicated, but this is easily remedied by wrapping it into a non-Copy struct). Second, even if I completely destructure Edge (i.e. by doing it in a separate scope), the reference to the graph is still there, even though it should have been moved (this must be a bug). It only works if I perform the destructuring in a separate function. Now, I have an idea how to circumvent it (by having a separate object that describes a state change and destructures the indices as they are supplied), but this becomes very awkward very quickly.

推荐答案

您还有一个您没有提到的问题:split 如何知道用户没有从不同的 Graph<传递Edge/代码>?幸运的是,使用更高等级的特征边界可以解决这两个问题!

You have a second problem that you didn’t mention: how does split know that the user didn’t pass an Edge from a different Graph? Fortunately, it’s possible to solve both problems with higher-rank trait bounds!

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

First, let’s have Edge carry a PhantomData marker instead of a real reference to the graph:

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

其次,让我们将所有 Graph 操作移动到一个新的 GraphView 对象中,该对象消耗应该使标识符无效的操作:>

Second, let’s move all the Graph operations into a new GraphView object that gets consumed by operations that should invalidate the identifiers:

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 的对象永远不会超过一个.

Now all we have to do is guard the construction of GraphView objects such that there’s never more than one with a given lifetime parameter 'a.

我们可以通过 (1) 强制 GraphView<'a> 成为 不变 超过 'aPhantomData 成员如上,并且 (2) 只提供构造的 GraphView 到具有更高等级特征边界的闭包,每次都会创建一个新的生命周期:

We can do this by (1) forcing GraphView<'a> to be invariant over 'a with a PhantomData member as above, and (2) only ever providing a constructed GraphView to a closure with a higher-rank trait bound that creates a fresh lifetime each time:

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);
    });
}

Rust Playground 上的完整演示.

这并不完全理想,因为调用者可能必须通过扭曲才能将其所有操作放入闭包中.但我认为这是我们在当前 Rust 语言中能做的最好的事情,它确实允许我们强制执行几乎没有其他语言可以表达的大量编译时保证.我很想看到对这种模式的更多人体工程学支持以某种方式添加到语言中——也许是一种通过返回值而不是闭包参数来创建新生命周期的方法 (pub fn view(&mut self) ->; 存在<'a> GraphView<'a>)?

This isn’t totally ideal, since the caller may have to go through contortions to put all its operations inside the closure. But I think it’s the best we can do in the current Rust language, and it does allow us to enforce a huge class of compile-time guarantees that almost no other language can express at all. I’d love to see more ergonomic support for this pattern added to the language somehow—perhaps a way to create a fresh lifetime via a return value rather than a closure parameter (pub fn view(&mut self) -> exists<'a> GraphView<'a>)?

这篇关于解构在函数参数中包含借位的结构的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆