1

I'm getting an error when trying to use a closure that does exactly the same as the print function below (in ln.9)

The error is the usual borrowed value does not live long enough. I've tried to replicate this in the playground but I can't. I'm certain that this is mainly because I don't really understand what's going on here so any help would be really appreciated.

What I can't understand is what is the difference between calling the print function and calling the check closure. They have exactly the same signature and even the same body.

How does the context in which they were created affect the borrow checker? What would be the solution to this?

extern crate typed_arena;
use typed_arena::Arena;

#[derive(Debug)]
struct AstNode<'a> {
    name: &'a str,
}

fn get_ast<'a>(path: &str, arena: &'a Arena<AstNode<'a>>) -> &'a AstNode<'a> {
   // ...
}

type CheckFn<'a> = dyn Fn(&'a AstNode<'a>);

fn print<'a>(root: &'a AstNode<'a>) {
    println!("{:?}", root);
}

fn it_does_not_have_details_if_all_ok<'a>(file: &str, check: Box<CheckFn<'a>>) {
    let arena = Arena::new();
    let a = &arena;
    let root = get_ast(file, a);
    println!("{:?}", root);
    // Works
    print(root);
    // Produces an error
    check(root);
}   

The error is:

error[E0597]: `arena` does not live long enough
  --> src/main.rs:21:14
   |
21 |     let a = &arena;
   |              ^^^^^ borrowed value does not live long enough
...
28 | }   
   | - borrowed value only lives until here
   |
note: borrowed value must be valid for the lifetime 'a as defined on the function body at 19:1...
  --> src/main.rs:19:1
   |
19 | fn it_does_not_have_details_if_all_ok<'a>(file: &str, check: Box<CheckFn<'a>>) {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
robertohuertasm
  • 846
  • 9
  • 17
  • 12
    Please post code rather than screenshots. It's much easier for someone to help you if they can copy/paste your code. – Peter Hall Aug 22 '18 at 14:48

1 Answers1

4

They have exactly the same signature and even the same body.

The body is not relevant because the type checker treats functions as a black box, and only looks at types. But, while the signatures may look the same, they are not. The difference is how the lifetime parameter is bound.

The lifetime parameter 'a of print<'a> is bound at the point that you call it. Since you are passing root as the argument, and root is a reference, you are implicitly instantiating 'a to be the lifetime of that reference. This is exactly what you want because root lives longer than the call to print.

But the lifetime parameter 'a of check<'a> is bound before you call it. Instead, you have bound it to the lifetime parameter of the function it_does_not_have_details_if_all_ok<'a>, which is determined by the caller of it_does_not_have_details_if_all_ok, so could be any lifetime that is longer than this function call. This is definitely not what you want because:

  1. The reference root does not live that long (because it holds a reference to arena which is local to the function).
  2. The function check does not even need its argument to live that long.

This is exactly the same as the reason why you can't return a reference to a variable created in a function. The difference is that you don't actually even need this lifetime constraint.

I can't easily test this out because you only posted an image of your code, and you have a few definitions not provided. But the quick fix is to use a higher-ranked trait bound (HRTB) on CheckFn:

type CheckFn = dyn for<'a> Fn(&'a AstNode<'a>);

This gets rid of the need to bind 'a whenever you mention CheckFn. Instead, the lifetimes are bound at the point when the inner function is called, just like it is for print.

As pointed out in the comments, you can elide these lifetimes altogether:

type CheckFn = dyn Fn(&AstNode);

This will cause the type checker to infer the lifetimes slightly more generally than above:

type CheckFn = dyn for<'a, 'b> Fn(&'a AstNode<'b>);
Peter Hall
  • 53,120
  • 14
  • 139
  • 204
  • Thanks Peter! Your suggestion worked as you described! I didn't know about the Higher-ranked trait bound and I was going crazy trying to find a way to change the bound lifetime of the closure. I really appreciate your input and your thorough explanation! :D – robertohuertasm Aug 22 '18 at 19:35
  • 1
    @robertohuertasm, omitting lifetimes from `CheckFn` (`type CheckFn = dyn Fn(&AstNode)`) should work too. [Lifetime elision rules](https://doc.rust-lang.org/reference/lifetime-elision.html) kick in and to the right thing. – red75prime Aug 23 '18 at 07:39
  • I've tried to omit the lifetimes but it doesn't work for me. I get this error: `expected signature of for<'r, 's> fn(&'r comrak::arena_tree::Node<'s, std::cell::RefCell>) -> _` , `found signature of for<'a> fn(&'a comrak::arena_tree::Node<'a, std::cell::RefCell>) -> _`. Note that it works as intended using HRTB – robertohuertasm Aug 23 '18 at 09:00
  • @robertohuertasm It works for me. It's very hard to comment further because your question did not provide a [mcve]. – Peter Hall Aug 23 '18 at 09:03
  • @PeterHall, this is from a project I'm working on to learn Rust. It's published in Github. You can download it if you want. The line with the closure type is [here](https://github.com/robertohuertasm/rusty-markdownlint/blob/master/rusty-markdownlint/src/ruleset.rs#L8). Sorry for not being able to provide anything better. I want to reiterate my appreciation for your help :P – robertohuertasm Aug 23 '18 at 09:10