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 !
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 :
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 :
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(||
| ^^^^^^^^^
|
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.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 unFnOnce
) 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 :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 def
.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 :