Destructuring a struct containing a borrow in a function argument

Deadly 提交于 2019-12-05 06:09:11

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!

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

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

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.

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

Full demo on Rust Playground.

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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!