Estoy confundido acerca de lo que está pasando con las vidas a continuación:
struct Foo{} impl Foo { fn foo(&self, _s: &str) {} } fn main() { let foo = &Foo{}; let closure = |s| foo.foo(s); // Part 1: error message about wrong lifetime in FnOnce take_closure(closure); // Part 2: no error when inlined take_closure(|s| foo.foo(s)); // Part 3: no error when `dyn`d and given explicit signature let closure: &dyn Fn(&str) -> _ = &|s| foo.foo(s); take_closure(closure); } fn take_closure(f: impl Fn(&str) -> ()) { let s = get_string(); f(&s) } fn get_string() -> String { "".to_string() }dyn es feo y me hace preguntarme qué es lo que realmente hace.¿Por qué falla la Parte 1?
La inferencia de tipos de Rust no es excelente para decidir qué tipo debe tener un cierre, cuando el cierre se declara por separado de donde se usa. Cuando el cierre acepta referencias, el compilador a menudo asume que habrá algún tiempo de vida específico que estará involucrado, no “cualquier tiempo de vida que la persona que llama quiera proporcionar”, como realmente se requiere aquí.
De hecho, hay un Rust RFC activo para mejorar esto al agregar otra forma de especificar parámetros de vida útil en los cierres. (El RFC también contiene un ejemplo en el que hacer la suposición de vida útil opuesta no funcionaría).
¿Qué sucede realmente en la parte 3? ¿Rust hace una vtable?
Sí, hay una vtable involucrada cada vez que usa dyn . Eso no es especialmente relevante para la causa raíz aquí; es solo que el tiempo de vida elidido en dyn Fn(&str) se resolvió de la manera que necesitaba en lugar de la forma en que no lo hizo.
¿Hay una mejor manera? Inlining es feo y
dynes feo y me hace preguntarme qué es lo que realmente hace.
Colocar un cierre directamente en la expresión de llamada a la función que lo usa es un estilo muy común de Rust, y le recomiendo que se ciña a él siempre que sea posible, ya que también es la forma que funciona bien con la inferencia de tipos.
Como solución alternativa en el caso de que necesite usar un cierre más de una vez, puede pasar el cierre a través de una función que restrinja su tipo:
fn string_acceptor<F: Fn(&str) -> ()>(f: F) -> F { f } ... let foo = &Foo{}; let closure = string_acceptor(|s| foo.foo(s));