Reputation: 37493
I'm trying to create a ScopeRunner type that can store method calls to a method of types implementing the Scope trait like this:
trait Scope {
fn run(&self) -> String;
}
struct ScopeImpl;
impl Scope for ScopeImpl {
fn run(&self) -> String {
"Some string".to_string()
}
}
struct ScopeRunner {
runner: Box<dyn Fn() -> String>,
}
impl ScopeRunner {
fn new<S: Scope>(scope: S) -> Self {
ScopeRunner {
runner: Box::new(move || scope.run())
}
}
pub fn run(self) -> String {
(self.runner)()
}
}
fn main() {
let scope = ScopeImpl {};
let scope_runner = ScopeRunner::new(scope);
dbg!(scope_runner.run());
}
I would expect that since ScopeRunner::new
creates a move closure this would cause scope to be moved into the closure. But instead the borrow checker gives me this error:
error[E0310]: the parameter type `S` may not live long enough
--> src/main.rs:21:30
|
20 | fn new<S: Scope>(scope: S) -> Self {
| -- help: consider adding an explicit lifetime bound `S: 'static`...
21 | ScopeRunner {runner: Box::new(move || scope.run())}
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
note: ...so that the type `[closure@src/main.rs:21:39: 21:58 scope:S]` will meet its required lifetime bounds
--> src/main.rs:21:30
|
21 | ScopeRunner {runner: Box::new(move || scope.run())}
|
When I replace ScopeRunner::new
with a non generic version that just takes a ScopeImpl
this code does work.
fn new(scope: ScopeImpl) -> Self {
ScopeRunner {
runner: Box::new(move || scope.run())
}
}
I don't understand why this is different. To me it seems the lifetime of the generic Scope
would be the same as the concrete version.
Upvotes: 0
Views: 68
Reputation: 58855
The problem is that S
could be any type with a Scope
impl, which includes all kinds of not-yet-existing types that carry references to other types. For example you could have an implementation like this:
struct AnotherScope<'a> {
reference: &'str,
}
impl Scope for ScopeImpl {
fn run(&self) -> String {
self.reference.to_string()
}
}
Rust is cautious and wants to make sure that this will work for any qualifying S
, including if it contains references.
The easiest fix is to do as the error note suggests and just disallow S
from having any non-static references:
fn new<S: Scope + 'static>(scope: S) -> Self {
ScopeRunner {
runner: Box::new(move || scope.run())
}
}
Bounding S
with 'static
effectively means that S
can contain either references to values with a 'static
lifetime or no references at all.
If you want to be a bit more flexible, you can broaden that to references that outlive the ScopeRunner
itself:
struct ScopeRunner<'s> {
runner: Box<dyn Fn() -> String + 's>,
}
impl<'s> ScopeRunner<'s> {
fn new<S: Scope + 's>(scope: S) -> Self {
ScopeRunner {
runner: Box::new(move || scope.run())
}
}
pub fn run(self) -> String {
(self.runner)()
}
}
Upvotes: 3