Rust: Erreur incorrecte : impossible de sortir de la variable externe capturée dans une fermeture `FnMut`

Créé le 29 janv. 2018  ·  3Commentaires  ·  Source: rust-lang/rust

erreur : impossible de sortir de la variable externe capturée dans une fermeture 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

Mais le deuxième cas devrait fonctionner aussi !

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

Commentaire le plus utile

@Boscop car vous ne pouvez déplacer les choses qu'une seule fois - une fermeture FnMut peut être invoquée plus d'une fois.

(Btw, l'erreur devrait alors dire qu'il doit s'agir de FnOnce.)

Je ne suis pas tout à fait d'accord, mais je ne suis pas non plus en désaccord . Ce genre d'erreur est souvent difficile de décider comment diagnostiquer. Il y a ici une sorte de tension : la fermeture veut faire une action (un mouvement), mais la signature de f ne le permet pas (car elle nécessite une fermeture qui peut être invoquée plus d'une fois). Il se peut que la signature soit erronée (elle devrait être généralisée pour accepter un FnOnce ) ou que la fermeture soit erronée (elle essaie de faire des choses qu'elle ne devrait pas faire). Au minimum, nous ne faisons clairement pas un bon travail pour capturer cette « tension » dans le diagnostic.

Fait intéressant, si la fermeture n'est pas donnée directement comme argument à f nous donnons une erreur un peu meilleure :

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

C'est à cause du suivi que @estebank (je pense ? ou était-ce quelqu'un d'autre...) mis en place pour savoir pourquoi nous décidons du "caractère déterminant" d'une fermeture. À l'heure actuelle, cette vérification n'entre pas en jeu car nous décidons que la fermeture devrait être FnMut uniquement à cause de la signature de f .

Cela se passe dans ce code :

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

Lorsque nous empruntons cette voie, nous ne finissons jamais par stocker "l'origine" du genre. En revanche, l'inférence upvar qui se déclencherait autrement fait ceci :

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

Nous avons donc probablement besoin de stocker des informations dans closure_kind_origins_mut , puis d'améliorer le chèque d'emprunt. Ensuite, nous pourrions donner une erreur comme :

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(||
   |         ^^^^^^^^^
   |            

Tous les 3 commentaires

Cela fonctionne si vous changez le FnMut en FnOnce.

Mais pourquoi ça ne marche pas aussi avec FnMut ?

(Btw, l'erreur devrait alors dire qu'il doit s'agir de FnOnce.)

@Boscop car vous ne pouvez déplacer les choses qu'une seule fois - une fermeture FnMut peut être invoquée plus d'une fois.

(Btw, l'erreur devrait alors dire qu'il doit s'agir de FnOnce.)

Je ne suis pas tout à fait d'accord, mais je ne suis pas non plus en désaccord . Ce genre d'erreur est souvent difficile de décider comment diagnostiquer. Il y a ici une sorte de tension : la fermeture veut faire une action (un mouvement), mais la signature de f ne le permet pas (car elle nécessite une fermeture qui peut être invoquée plus d'une fois). Il se peut que la signature soit erronée (elle devrait être généralisée pour accepter un FnOnce ) ou que la fermeture soit erronée (elle essaie de faire des choses qu'elle ne devrait pas faire). Au minimum, nous ne faisons clairement pas un bon travail pour capturer cette « tension » dans le diagnostic.

Fait intéressant, si la fermeture n'est pas donnée directement comme argument à f nous donnons une erreur un peu meilleure :

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

C'est à cause du suivi que @estebank (je pense ? ou était-ce quelqu'un d'autre...) mis en place pour savoir pourquoi nous décidons du "caractère déterminant" d'une fermeture. À l'heure actuelle, cette vérification n'entre pas en jeu car nous décidons que la fermeture devrait être FnMut uniquement à cause de la signature de f .

Cela se passe dans ce code :

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

Lorsque nous empruntons cette voie, nous ne finissons jamais par stocker "l'origine" du genre. En revanche, l'inférence upvar qui se déclencherait autrement fait ceci :

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

Nous avons donc probablement besoin de stocker des informations dans closure_kind_origins_mut , puis d'améliorer le chèque d'emprunt. Ensuite, nous pourrions donner une erreur comme :

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(||
   |         ^^^^^^^^^
   |            
Cette page vous a été utile?
0 / 5 - 0 notes