1

The basic idea of the code is to create a hierarchical struct Context which contains some information of symbols, and a Context will be provided to the lambda in a Statement struct to get the final result. A child context can be derived from the parent if needed:

use anyhow::Result; // 1.0.40
use std::{collections::HashMap, rc::Rc};

struct Context<'a> {
    parent: Option<&'a mut Context<'a>>,
    symbols: HashMap<String, i64>,
}

impl<'a> Context<'a> {
    fn new() -> Self {
        Context {
            parent: None,
            symbols: HashMap::new(),
        }
    }

    fn derive(&'a mut self) -> Self {
        Context {
            parent: Some(self),
            symbols: HashMap::new(),
        }
    }
}

#[derive(Clone)]
struct Statement {
    execute: Rc<dyn Fn(&mut Context) -> Result<()>>,
}

struct Node {
    cond: Statement,
    stmts: Vec<Statement>,
}

impl Node {
    fn get(&self) -> Statement {
        let cond = self.cond.clone();
        let stmts = self.stmts.clone();
        Statement {
            execute: Rc::new(move |ctx| {
                (cond.execute)(ctx)?;
                let mut cctx = ctx.derive();
                for stmt in stmts {
                    (stmt.execute)(&mut cctx)?;
                }
                Ok(())
            }),
        }
    }
}

When I compile this code, I get the error:

error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements
  --> src/lib.rs:42:36
   |
42 |                 let mut cctx = ctx.derive();
   |                                    ^^^^^^
   |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the body at 40:30...
  --> src/lib.rs:40:30
   |
40 |               execute: Rc::new(move |ctx| {
   |  ______________________________^
41 | |                 (cond.execute)(&mut ctx)?;
42 | |                 let mut cctx = ctx.derive();
43 | |                 for stmt in stmts {
...  |
46 | |                 Ok(())
47 | |             }),
   | |_____________^
note: ...so that reference does not outlive borrowed content
  --> src/lib.rs:42:32
   |
42 |                 let mut cctx = ctx.derive();
   |                                ^^^
note: but, the lifetime must be valid for the anonymous lifetime #2 defined on the body at 40:30...
  --> src/lib.rs:40:30
   |
40 |               execute: Rc::new(move |ctx| {
   |  ______________________________^
41 | |                 (cond.execute)(&mut ctx)?;
42 | |                 let mut cctx = ctx.derive();
43 | |                 for stmt in stmts {
...  |
46 | |                 Ok(())
47 | |             }),
   | |_____________^
note: ...so that the types are compatible
  --> src/lib.rs:42:36
   |
42 |                 let mut cctx = ctx.derive();
   |                                    ^^^^^^
   = note: expected `&mut Context<'_>`
              found `&mut Context<'_>`

I find this error message is useless. How can I fix this error?

YLonely
  • 11
  • 2

2 Answers2

1

With your current definition of execute, the lifetimes of Context and the &mut parameter are implied to be different due to lifetime elision rules but your use of .derive() within the closure requires them to be the same.

dyn Fn(&'a mut Context<'b>) -> Result<()>

You can fix this by using a higher-ranked trait bound to introduce a named lifetime to link them together:

dyn for<'a> Fn(&'a mut Context<'a>) -> Result<()>

However, fixing that and making the for loop not consume stmts (playground), you still have lifetime problems:

error[E0499]: cannot borrow `*ctx` as mutable more than once at a time
  --> src/lib.rs:42:32
   |
40 |             execute: Rc::new(move |ctx| {
   |                                    --- has type `&'1 mut Context<'1>`
41 |                 (cond.execute)(ctx)?;
   |                 -------------------
   |                 |              |
   |                 |              first mutable borrow occurs here
   |                 argument requires that `*ctx` is borrowed for `'1`
42 |                 let mut cctx = ctx.derive();
   |                                ^^^ second mutable borrow occurs here

error[E0499]: cannot borrow `cctx` as mutable more than once at a time
  --> src/lib.rs:44:36
   |
40 |             execute: Rc::new(move |ctx| {
   |                                    --- has type `&'1 mut Context<'1>`
...
44 |                     (stmt.execute)(&mut cctx)?;
   |                     ---------------^^^^^^^^^-
   |                     |              |
   |                     |              `cctx` was mutably borrowed here in the previous iteration of the loop
   |                     argument requires that `cctx` is borrowed for `'1`

Which is answered by Why does this mutable borrow live beyond its scope? The short of it is, don't do &'a mut Context<'a>, it wreaks havoc on the borrow checker trying to make distinct lifetimes all line up and will inevitably cause confusing errors.

Using mutable references in particular are more limited, so you may get away with it by using immutable references, &'a Context<'a>, but depending on what you intend to do that might not be an option. If you need mutability, you may have to resort to shared ownership and interior mutability via Rc and RefCell.

kmdreko
  • 42,554
  • 6
  • 57
  • 106
0

So the problem has to do with lifetimes, but that's by far not the only problem.

First, whenever I run into errors with closures and weird compiler messages, I replace the closure with an actual function, for the time being, as that tends to make certain error messages easier to parse.

Then I tried adding explicit lifetime parameters to lots of other parts in your code:

https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=0f5bb5010ea26cff3f99237b4439eba2

use anyhow::Result; // 1.0.40
use std::{collections::HashMap, rc::Rc};

struct Context<'a> {
    parent: Option<&'a mut Context<'a>>,
    symbols: HashMap<String, i64>,
}

impl<'a> Context<'a> {
    fn new() -> Self {
        Context {
            parent: None,
            symbols: HashMap::new(),
        }
    }

    fn derive(&'a mut self) -> Context<'a> {
        Context {
            parent: Some(self),
            symbols: HashMap::new(),
        }
    }
}

#[derive(Clone)]
struct Statement<'a> {
    execute: Rc<(dyn Fn(&'a mut Context<'a>) -> Result<()> + 'a)>,
}

struct Node<'a> {
    cond: Statement<'a>,
    stmts: Vec<Statement<'a>>,
}

impl<'a> Node<'a> {
    fn get(&self) -> Statement<'a> {
        let cond = self.cond.clone();
        let stmts = self.stmts.clone();
        Statement {
             execute: Rc::new(move |ctx| {
                (cond.execute)(ctx)?;
                let mut cctx = ctx.derive();
                for stmt in stmts {
                    (stmt.execute)(&mut cctx)?;
                }
                Ok(())
            }),
        }
    }
}

That solves the lifetime issue, but there's a bunch of other issues, too: You're generously using mutable references (mutable borrows) and of course now the compiler complains that you are borrowing mutably more than once, because your lifetimes require that the references stay alive for long enough!

The problem here is that you'd need to keep multiple mutable references to a given context alive at the same time, which Rust doesn't allow.

The solution to this conundrum rather than trying to hack away at the current structure, is to rethink if you really need all those mutable references, and if you can rework the code in some other way, too.

From other object oriented languages it's quite common to have this "sea of objects" with lots of (mutable, of course) references pointing back and forth, but Rust doesn't quite like that.

cadolphs
  • 9,014
  • 1
  • 24
  • 41