erro: não é possível sair da variável externa capturada em um fechamento
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
Mas o segundo caso também deve funcionar!
Funciona se você alterar o FnMut para FnOnce.
Mas por que também não funciona com o FnMut?
(Btw, o erro deveria dizer que tem que ser FnOnce então.)
@Boscop porque você só pode mover coisas uma vez - um fechamento FnMut
pode ser invocado mais de uma vez.
(Btw, o erro deveria dizer que tem que ser FnOnce então.)
Não concordo inteiramente, mas também não discordo . Geralmente, é difícil decidir como diagnosticar esse tipo de erro. Há um tipo de tensão aqui: o fechamento quer realizar uma ação (um movimento), mas a assinatura de f
não o permite (porque requer um fechamento que pode ser invocado mais de uma vez). Pode ser que a assinatura esteja errada (deve ser generalizada para aceitar um FnOnce
) ou que o fechamento esteja errado (ele está tentando fazer coisas que não deveria). No mínimo, claramente não estamos fazendo um bom trabalho em capturar essa 'tensão' no diagnóstico.
Curiosamente, se o fechamento não for dado diretamente como um argumento para f
, daremos um erro um pouco melhor :
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;
|
Isso ocorre por causa do rastreamento que @estebank (eu acho? Ou foi outra pessoa ...) colocou para rastrear porque decidimos a "característica definidora" de um fechamento. No momento, essa verificação não está entrando em jogo porque estamos decidindo que o fechamento deve ser FnMut
unicamente por causa da assinatura de f
.
Isso acontece neste código:
Quando seguimos por esse caminho, nunca acabamos armazenando a "origem" do tipo. Em contraste, a inferência upvar que, de outra forma, teria efeito:
Portanto, provavelmente precisamos armazenar algum tipo de informação em closure_kind_origins_mut
e, em seguida, melhorar o cheque de empréstimo. Então, poderíamos dar um erro 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(||
| ^^^^^^^^^
|
Comentários muito úteis
@Boscop porque você só pode mover coisas uma vez - um fechamento
FnMut
pode ser invocado mais de uma vez.Não concordo inteiramente, mas também não discordo . Geralmente, é difícil decidir como diagnosticar esse tipo de erro. Há um tipo de tensão aqui: o fechamento quer realizar uma ação (um movimento), mas a assinatura de
f
não o permite (porque requer um fechamento que pode ser invocado mais de uma vez). Pode ser que a assinatura esteja errada (deve ser generalizada para aceitar umFnOnce
) ou que o fechamento esteja errado (ele está tentando fazer coisas que não deveria). No mínimo, claramente não estamos fazendo um bom trabalho em capturar essa 'tensão' no diagnóstico.Curiosamente, se o fechamento não for dado diretamente como um argumento para
f
, daremos um erro um pouco melhor :Isso ocorre por causa do rastreamento que @estebank (eu acho? Ou foi outra pessoa ...) colocou para rastrear porque decidimos a "característica definidora" de um fechamento. No momento, essa verificação não está entrando em jogo porque estamos decidindo que o fechamento deve ser
FnMut
unicamente por causa da assinatura def
.Isso acontece neste código:
https://github.com/rust-lang/rust/blob/70f7d5842f29d4900f24420b030f144d21f3c5fc/src/librustc_typeck/check/closure.rs#L151 -L155
Quando seguimos por esse caminho, nunca acabamos armazenando a "origem" do tipo. Em contraste, a inferência upvar que, de outra forma, teria efeito:
https://github.com/rust-lang/rust/blob/70f7d5842f29d4900f24420b030f144d21f3c5fc/src/librustc_typeck/check/upvar.rs#L182 -L188
Portanto, provavelmente precisamos armazenar algum tipo de informação em
closure_kind_origins_mut
e, em seguida, melhorar o cheque de empréstimo. Então, poderíamos dar um erro como: