How do I return a boxed closure from a method that

2019-05-10 15:21发布

问题:

I have a structure that contains a value and I want to obtain a function that operates on this value:

struct Returner {
    val: i32,
}

impl<'a> Returner {
    fn get(&'a self) -> Box<Fn(i32) -> i32> {
        Box::new(|x| x + self.val)
    }
}

This fails compilation:

error[E0495]: cannot infer an appropriate lifetime due to conflicting requirements
 --> src/main.rs:7:18
  |
7 |         Box::new(|x| x + self.val)
  |                  ^^^^^^^^^^^^^^^^
  |
note: first, the lifetime cannot outlive the lifetime 'a as defined on the impl at 5:1...
 --> src/main.rs:5:1
  |
5 | impl<'a> Returner {
  | ^^^^^^^^^^^^^^^^^
  = note: ...so that the types are compatible:
          expected &&Returner
             found &&'a Returner
  = note: but, the lifetime must be valid for the static lifetime...
  = note: ...so that the expression is assignable:
          expected std::boxed::Box<std::ops::Fn(i32) -> i32 + 'static>
             found std::boxed::Box<std::ops::Fn(i32) -> i32>

This is because the closure borrows self, which is fine by me, because I don't intend to use the obtained function after the struct is destroyed. From what I've gathered so far, there's two ways to make it possible:

  1. Use the move keyword. I don't want to use it because it will take ownership on the object, and want I to use it after it has returned this function.

  2. Explicitly specify the lifetime of the closure to tell the compiler that it has the same lifetime as the struct it was called from.

I think that 2 is the correct way in my situation, but I've not been able to find out how to specify the closure's lifetime. Is there a direct way to do it or I've got it all wrong and it contradicts Rust lifetime logic?

回答1:

In general, you can specify the lifetime of a boxed trait object by writing Box<Trait + 'a> and analogously for trait objects behind other kinds of pointers (if it's omitted, it defaults to 'static at least in the case of Box). So in this specific case you want the return type Box<(Fn(i32) -> i32) + 'a>.

However, when you do that you will see another error about self not living long enough. The reason is that (without move) the closure will capture a reference to the local variable self. The solution is to use move. This does not move the Returner object, it moves self which is a reference to the Returner object.

In summary:

struct Returner {
    val: i32,
}

impl<'a> Returner {
    fn get(&'a self) -> Box<Fn(i32) -> i32 + 'a> {
        Box::new(move |x| x + self.val)
    }
}


回答2:

As said in an existing answer:

  1. Add a lifetime that ties the lifetime of self to the lifetime of the returned value.
  2. Move the reference to self into the closure.

Since Rust 1.26, you no longer need to return a boxed closure if you are only returning a single type. Instead, you can use impl Trait:

impl Returner {
    fn get<'a>(&'a self) -> impl Fn(i32) -> i32 + 'a {
        move |x| x + self.val
    }
}

See also:

  • Why is adding a lifetime to a trait with the plus operator (Iterator<Item = &Foo> + 'a) needed?
  • What is the correct way to return an Iterator (or any other trait)?
  • Returning a closure from a function
  • Conditionally iterate over one of several possible iterators