Rust: Error incorrecto: no se puede salir de la variable externa capturada en un cierre `FnMut`

Creado en 29 ene. 2018  ·  3Comentarios  ·  Fuente: rust-lang/rust

error: no se puede salir de la variable externa capturada en un cierre FnMut

struct Foo {
    a: i32,
    b: i32,
    bar: Bar,
}

struct Bar;

impl Bar {
    fn f<F: FnMut()>(&self, mut f: F) {
        f();
    }
}

fn main() {
    let mut foo = Foo { a: 1, b: 2, bar: Bar };
    let a = &mut foo.a;
    let b = &mut foo.b;
    (|| { // works
        *if true {a} else {b} = 42;
    })();

    let mut foo = Foo { a: 1, b: 2, bar: Bar };
    let a = &mut foo.a;
    let b = &mut foo.b;
    foo.bar.f(|| { // doesn't work
        *if true {a} else {b} = 42;
    });
}

https://play.rust-lang.org/?gist=4ce6948a92c2fcb281b3cade8574691d&version=nightly

¡Pero el segundo caso también debería funcionar!

A-borrow-checker A-diagnostics C-enhancement T-compiler

Comentario más útil

@Boscop porque solo puede mover cosas una vez: un cierre FnMut se puede invocar más de una vez.

(Por cierto, el error debería decir que tiene que ser FnOnce entonces).

No estoy del todo de acuerdo, pero tampoco en desacuerdo . Este tipo de error a menudo es difícil de decidir cómo diagnosticar. Aquí hay una especie de tensión: el cierre quiere realizar una acción (un movimiento), pero la firma de f no lo permite (porque requiere un cierre que puede invocarse más de una vez). Puede ser que la firma sea errónea (debería generalizarse para aceptar un FnOnce ) o que el cierre sea incorrecto (está intentando hacer cosas que no debería hacer). Como mínimo, claramente no estamos haciendo un buen trabajo al capturar esta 'tensión' en el diagnóstico.

Curiosamente, si el cierre no se da directamente como argumento para f , damos un error algo mejor :

error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
  --> src/main.rs:19:19
   |
19 |       let closure = || { // doesn't work
   |  ___________________^
20 | |         *if true {a} else {b} = 42;
21 | |     };
   | |_____^
22 |       foo.bar.f(closure);
   |               - the requirement to implement `FnMut` derives from here
   |
note: closure is `FnOnce` because it moves the variable `a` out of its environment
  --> src/main.rs:20:19
   |
20 |         *if true {a} else {b} = 42;
   |                

Esto se debe al seguimiento que @estebank (creo? O fue otra persona ...) puso para rastrear por qué decidimos el "rasgo definitorio" de un cierre. En este momento, esa verificación no entra en juego porque estamos decidiendo que el cierre debe ser FnMut únicamente por la firma de f .

Eso sucede en este código:

https://github.com/rust-lang/rust/blob/70f7d5842f29d4900f24420b030f144d21f3c5fc/src/librustc_typeck/check/closure.rs#L151 -L155

Cuando vamos por ese camino, nunca terminamos almacenando el "origen" del tipo. En contraste, la inferencia de upvar que de otra manera se activaría hace esto:

https://github.com/rust-lang/rust/blob/70f7d5842f29d4900f24420b030f144d21f3c5fc/src/librustc_typeck/check/upvar.rs#L182 -L188

Así que probablemente necesitemos almacenar algún tipo de información en closure_kind_origins_mut y luego mejorar la comprobación de préstamo. Entonces podríamos dar un error como:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
  --> src/main.rs:20:19
   |
17 |     let a = &mut foo.a;
   |         - captured outer variable
...
20 |         *if true {a} else {b} = 42;
   |                   ^ cannot move out of captured outer variable in an `FnMut` closure
note: closure is `FnMut` because of the requirements of `f()`
  --> src/main.rs:19:19
   |
19 |         foo.bar.f(||
   |         ^^^^^^^^^
   |            

Todos 3 comentarios

Funciona si cambia FnMut a FnOnce.

Pero, ¿por qué no puede funcionar también con FnMut?

(Por cierto, el error debería decir que tiene que ser FnOnce entonces).

@Boscop porque solo puede mover cosas una vez: un cierre FnMut se puede invocar más de una vez.

(Por cierto, el error debería decir que tiene que ser FnOnce entonces).

No estoy del todo de acuerdo, pero tampoco en desacuerdo . Este tipo de error a menudo es difícil de decidir cómo diagnosticar. Aquí hay una especie de tensión: el cierre quiere realizar una acción (un movimiento), pero la firma de f no lo permite (porque requiere un cierre que puede invocarse más de una vez). Puede ser que la firma sea errónea (debería generalizarse para aceptar un FnOnce ) o que el cierre sea incorrecto (está intentando hacer cosas que no debería hacer). Como mínimo, claramente no estamos haciendo un buen trabajo al capturar esta 'tensión' en el diagnóstico.

Curiosamente, si el cierre no se da directamente como argumento para f , damos un error algo mejor :

error[E0525]: expected a closure that implements the `FnMut` trait, but this closure only implements `FnOnce`
  --> src/main.rs:19:19
   |
19 |       let closure = || { // doesn't work
   |  ___________________^
20 | |         *if true {a} else {b} = 42;
21 | |     };
   | |_____^
22 |       foo.bar.f(closure);
   |               - the requirement to implement `FnMut` derives from here
   |
note: closure is `FnOnce` because it moves the variable `a` out of its environment
  --> src/main.rs:20:19
   |
20 |         *if true {a} else {b} = 42;
   |                

Esto se debe al seguimiento que @estebank (creo? O fue otra persona ...) puso para rastrear por qué decidimos el "rasgo definitorio" de un cierre. En este momento, esa verificación no entra en juego porque estamos decidiendo que el cierre debe ser FnMut únicamente por la firma de f .

Eso sucede en este código:

https://github.com/rust-lang/rust/blob/70f7d5842f29d4900f24420b030f144d21f3c5fc/src/librustc_typeck/check/closure.rs#L151 -L155

Cuando vamos por ese camino, nunca terminamos almacenando el "origen" del tipo. En contraste, la inferencia de upvar que de otra manera se activaría hace esto:

https://github.com/rust-lang/rust/blob/70f7d5842f29d4900f24420b030f144d21f3c5fc/src/librustc_typeck/check/upvar.rs#L182 -L188

Así que probablemente necesitemos almacenar algún tipo de información en closure_kind_origins_mut y luego mejorar la comprobación de préstamo. Entonces podríamos dar un error como:

error[E0507]: cannot move out of captured outer variable in an `FnMut` closure
  --> src/main.rs:20:19
   |
17 |     let a = &mut foo.a;
   |         - captured outer variable
...
20 |         *if true {a} else {b} = 42;
   |                   ^ cannot move out of captured outer variable in an `FnMut` closure
note: closure is `FnMut` because of the requirements of `f()`
  --> src/main.rs:19:19
   |
19 |         foo.bar.f(||
   |         ^^^^^^^^^
   |            
¿Fue útil esta página
0 / 5 - 0 calificaciones