Lifetime issues with a closure argument in Rust

佐手、 提交于 2021-02-08 06:53:21

问题


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>>) {
   | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

回答1:


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


来源:https://stackoverflow.com/questions/51969236/lifetime-issues-with-a-closure-argument-in-rust

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