Rust: Неправильная ошибка: невозможно выйти из захваченной внешней переменной при закрытии `FnMut`

Созданный на 29 янв. 2018  ·  3Комментарии  ·  Источник: rust-lang/rust

ошибка: невозможно выйти из захваченной внешней переменной при закрытии 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

Но и второй случай тоже должен работать!

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

Самый полезный комментарий

@Boscop, потому что вы можете перемещать объекты только один раз - закрытие FnMut можно вызывать более одного раза.

(Кстати, тогда в ошибке должно быть указано, что это FnOnce.)

Я не совсем согласен, но и не возражаю . Часто бывает сложно решить, как диагностировать такую ​​ошибку. Здесь есть своего рода напряжение: замыкание хочет выполнить действие (ход), но подпись f не разрешает его (потому что для этого требуется замыкание, которое может быть вызвано более одного раза). Может случиться так, что подпись ошибочна (она должна быть обобщена для принятия FnOnce ) или что закрытие неправильное (оно пытается делать то, чего не должно делать). Как минимум, мы явно не очень хорошо справляемся с этим «напряжением» в диагностике.

Интересно, что если замыкание не указано напрямую в качестве аргумента для f мы даем несколько лучшую ошибку :

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

Это из-за отслеживания, которое @estebank (я думаю? Или это был кто-то другой ...) ввел, чтобы отследить, почему мы выбираем «определяющую черту» закрытия. Прямо сейчас эта проверка не применяется, потому что мы решаем, что закрытие должно быть FnMut исключительно из-за подписи f .

Это происходит в этом коде:

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

Когда мы идем по этому пути, мы никогда не запоминаем «происхождение» этого вида. Напротив, вывод upvar, который в противном случае сработал бы, делает следующее:

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

Поэтому нам, вероятно, нужно сохранить какую-то информацию в closure_kind_origins_mut а затем улучшить проверку заимствования. Тогда мы могли бы выдать такую ​​ошибку:

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

Все 3 Комментарий

Это работает, если вы измените FnMut на FnOnce.

Но почему он не может работать и с FnMut?

(Кстати, тогда в ошибке должно быть указано, что это FnOnce.)

@Boscop, потому что вы можете перемещать объекты только один раз - закрытие FnMut можно вызывать более одного раза.

(Кстати, тогда в ошибке должно быть указано, что это FnOnce.)

Я не совсем согласен, но и не возражаю . Часто бывает сложно решить, как диагностировать такую ​​ошибку. Здесь есть своего рода напряжение: замыкание хочет выполнить действие (ход), но подпись f не разрешает его (потому что для этого требуется замыкание, которое может быть вызвано более одного раза). Может случиться так, что подпись ошибочна (она должна быть обобщена для принятия FnOnce ) или что закрытие неправильное (оно пытается делать то, чего не должно делать). Как минимум, мы явно не очень хорошо справляемся с этим «напряжением» в диагностике.

Интересно, что если замыкание не указано напрямую в качестве аргумента для f мы даем несколько лучшую ошибку :

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

Это из-за отслеживания, которое @estebank (я думаю? Или это был кто-то другой ...) ввел, чтобы отследить, почему мы выбираем «определяющую черту» закрытия. Прямо сейчас эта проверка не применяется, потому что мы решаем, что закрытие должно быть FnMut исключительно из-за подписи f .

Это происходит в этом коде:

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

Когда мы идем по этому пути, мы никогда не запоминаем «происхождение» этого вида. Напротив, вывод upvar, который в противном случае сработал бы, делает следующее:

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

Поэтому нам, вероятно, нужно сохранить какую-то информацию в closure_kind_origins_mut а затем улучшить проверку заимствования. Тогда мы могли бы выдать такую ​​ошибку:

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(||
   |         ^^^^^^^^^
   |            
Была ли эта страница полезной?
0 / 5 - 0 рейтинги