A weird error lifetime problem with closure

Hi guys:

I'm a newbie, and this is probably the hardest code I have ever written. But it has a bug in it :rofl:

fn main() {
    let v = vec![Json::True, Json::Obj(vec![])];
    let mut tree = Dag::new(&v);

    let e = Editor::new(&mut tree);
    let t = (e.tree.node)('t');
    println!("{:?}", t.unwrap());
}

trait Ast<'a>: Sized {
    fn node_function() -> Box<dyn Fn(char) -> Option<Self> + 'a>;
}

struct Dag<'a, Node: Ast<'a>> {
    x: &'a Vec<Node>,
    node: Box<dyn Fn(char) -> Option<Node> + 'a>,
}

impl<'a, Node: Ast<'a>> Dag<'a, Node> {
    fn new(x: &'a Vec<Node>) -> Self {
        Dag {
            x,
            node: Node::node_function(),
        }
    }
}

#[derive(Debug)]
enum Json<'a> {
    True,
    False,
    Obj(Vec<&'a Json<'a>>),
}

impl<'a> Ast<'a> for Json<'a> {
    fn node_function() -> Box<dyn Fn(char) -> Option<Self> + 'a> {
        Box::new(move |x| match x {
            't' => Some(Json::True),
            'f' => Some(Json::False),
            _ => None,
        })
    }
}

struct Editor<'a, Node: Ast<'a>> {
    tree: &'a mut Dag<'a, Node>,
}

impl<'a, Node: Ast<'a> + 'a> Editor<'a, Node> {
    fn new(tree: &'a mut Dag<'a, Node>) -> Editor<'a, Node> {
        Editor { tree }
    }
}

Playground

I'm working on this code, the general idea is: I want Struct Dag to store a closure, and for different data type, closure will be different.

The problem happens when I mutable borrow Dag. Compiler doesn't provide much useful information.

If you change Dag, Json, and Editor to own their fields instead of borrowing them (which is always a good default: as @kornel says, putting references in structs is an advanced Rust technique), and make all the downstream changes necessary to accomodate that, your code compiles: playground.

2 Likes

You almost never want a reference with the same lifetime as the struct it points to. Change that &'a mut Dag<'a, Node> to &'b mut Dag<'a, Node> and you should be fine. Playground

3 Likes

Thank you for you help. Is there a way to keep the reference? I have other codes that work with this one. And drop the reference in here will be super painful.

@SkiFire13's answer shows how to fix the error without changing your structs. But I'd guess that storing owned values instead will save you a lot of trouble down the road.

Yes!!! This is the solution I was looking for. Big thanks man!

BTW why it is not a good idea to use struct with reference?

A struct (or enum) that holds a reference cannot exist independently, because the reference points to data stored elsewhere, which must be valid for the struct to be used. If you keep &mut Vec<T> in your type instead of Vec<T>, the borrow checker will ensure that a value of that type never references a vector that's already been freed or that other references may point to (because &mut means exclusive access). Writing code that obeys the borrow checker's rules can be difficult -- as you've just experienced -- and the more references and lifetimes you have laying around the more difficult it becomes, and the less comprehensible the compiler's error messages will be.

But when your type owns all its fields, its values can exist on their own without any validity requirements that involve external data. There are fewer ways for manipulating those values to fall afoul of the compiler's rules, and the remaining compiler errors will be easier to interpret.

Sometimes keeping a reference in a struct is the right thing to do: a typical example is when you're writing code to parse a string into a Rust data structure, and you want to store references to portions of the original string instead of copying those bytes into a new owned String ("zero-copy" is the slogan for this). But I'd say that you should default to storing owned values and only use references if you have a concrete reason to do so, like wanting to avoid copying data in specific places. I think a good rule to follow is that if your type has a lifetime annotation <'a>, you should be able to describe what 'a means in terms of the functionality of that type, for example "'a is the lifetime for our borrow of portions of the string being parsed". If you can't explain and justify your lifetime annotations in that way then you might want to rethink the decision to store a reference.

2 Likes

It is a good idea – if/when you really need it and know how to do it correctly. Or I should say, it's not a bad idea on its own. However, it is the general observation that beginners tend to trip up on the necessary explicit lifetime annotations.

Besides, there are all the good points @cole-miller mentioned above – Rust is not an object-oriented language, it's all about values, not identity. References are for providing views into values, for reasons of indirection, efficiency, or something else. But owned values should provide the ground truth in your code. When in doubt, clone()!

2 Likes

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.