Почему временная часть выражения в конце блока является ошибкой?

Скорее всего, это хрестоматийный случай, когда я не понимаю некоторых технических особенностей средства проверки заимствований, но было бы неплохо, если бы кто-нибудь мог прояснить это для меня.

У меня есть этот (невероятно упрощенный) фрагмент кода, который отлично компилируется.

pub struct Example(pub Vec<String>);

impl Example {
  pub fn iter(&self) -> impl Iterator<Item=&String> {
    self.0.iter()
  }
}

pub fn some_condition(_: &str) -> bool {
  // This is not important.
  return false;
}

pub fn foo() -> bool {
  let example = Example(vec!("foo".to_owned(), "bar".to_owned()));
  let mut tmp = example.iter();
  tmp.all(|x| some_condition(x))
}

pub fn main() {
  println!("{}", foo());
}

Однако первое, что я попробовал (что, на мой взгляд, должно быть эквивалентно приведенному выше), - это полностью исключить временную переменную tmp, как показано ниже.

pub fn foo() -> bool {
  let example = Example(vec!("foo".to_owned(), "bar".to_owned()));
  example.iter().all(|x| some_condition(x))
}

Но эта версия выдает следующую ошибку.

error[E0597]: `example` does not live long enough
  --> so_temporary.rs:23:3
   |
23 |   example.iter().all(|x| some_condition(x))
   |   ^^^^^^^-------
   |   |
   |   borrowed value does not live long enough
   |   a temporary with access to the borrow is created here ...
24 | }
   | -
   | |
   | `example` dropped here while still borrowed
   | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `impl std::iter::Iterator`
   |
   = note: The temporary is part of an expression at the end of a block. Consider forcing this temporary to be dropped sooner, before the block's local variables are dropped. For example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block.

Теперь, очевидно, примечание в конце ошибки - отличное предложение, и поэтому я ввел временное средство для устранения проблемы. Но я не понимаю почему решает проблему. Чем отличается время жизни моей tmp переменной от example.iter(), встроенной непосредственно в выражение, из-за чего один работает, а другой - нет?


person Silvio Mayolo    schedule 30.01.2021    source источник
comment
Я упростил и отладил ваш пример здесь. Это действительно странно, я думаю, это можно квалифицировать как ошибку компилятора.   -  person pretzelhammer    schedule 30.01.2021
comment
@pretzelhammer И это легко исправить в std, не касаясь компилятора. play.rust-lang.org/   -  person Peter Hall    schedule 30.01.2021
comment
Хотя забавно, что это работает вообще без дополнительной привязки.   -  person Peter Hall    schedule 30.01.2021
comment
@PeterHall Теперь я запутался вдвойне. Почему это исправляет? o.O   -  person Silvio Mayolo    schedule 30.01.2021
comment
@PeterHall ошибка компилятора, на мой взгляд, заключается в том, что я ожидал, что и impl Iterator + '_, и impl Iterator<Item = &i32>, используемые в позиции возврата функции, будут разрешены в конкретный тип std::slice::Iter<'_, i32> и будут вести себя идентично конкретному типу во всех сценариях, без каких-либо дополнительных хаков. или обходные пути.   -  person pretzelhammer    schedule 31.01.2021


Ответы (1)


Это, по сути, тот же ответ, что и Почему я получаю, что в возвращаемом значении недостаточно долго?, и это несколько объясняется в ошибке сам, но я уточню. Это поведение аналогично нормальному выражению блока:

pub struct Example(pub Vec<String>);

impl Example {
    pub fn iter(&self) -> impl Iterator<Item=&String> {
        self.0.iter()
    }
}

pub fn main() {
    let foo = {
        let example = Example(vec!("foo".to_owned(), "".to_owned()));
        example.iter().all(String::is_empty)
    };
    println!("{}", foo);
}
error[E0597]: `example` does not live long enough
  --> src/main.rs:12:9
   |
12 |         example.iter().all(String::is_empty)
   |         ^^^^^^^-------
   |         |
   |         borrowed value does not live long enough
   |         a temporary with access to the borrow is created here ...
13 |     };
   |     -- ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `impl Iterator`
   |     |
   |     `example` dropped here while still borrowed
   |
   = note: the temporary is part of an expression at the end of a block;
           consider forcing this temporary to be dropped sooner, before the block's local variables are dropped
help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block
   |
12 |         let x = example.iter().all(String::is_empty); x
   |         ^^^^^^^                                     ^^^

объем временных значений часто является оператором , в котором они были созданы. В приведенном выше коде example - это переменная, которая уничтожается в конце блока. Однако example.iter() создает временный impl Iterator, и его временной областью является полный оператор let foo = .... Итак, шаги при оценке этого:

  • оценить результат example.iter().all(...)
  • падение example
  • присвоить результат foo
  • падение impl Iterator

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

Временные объекты, которые создаются в последнем выражении тела функции, удаляются после любых именованных переменных, связанных в теле функции, поскольку меньшая ограничивающая временная область видимости отсутствует.

По поводу комментариев:

  • Причина, по которой он работает, когда impl Iterator заменяется на std::slice::Iter<'_, i32> (в примере с претцельхаммером), заключается в том, что средство проверки падения знает, что slice::Iter не получает доступ к example при сбросе, тогда как он должен предполагать, что impl Iterator имеет.

  • Причина, по которой он работает с fn my_all(mut self, ...) (в примере Питера Холла), заключается в том, что all принимает итератор по ссылке, а my_all - по значению. Временное impl Iterator потребляется и уничтожается до конца выражения.

Из рассмотрения различных проблем Rust, связанных с этим, становится ясно, что некоторые сочтут это ошибкой. Определенно не очевидно, что { ...; EXPR } и { ...; let x = EXPR; x } могут быть разными. Однако, поскольку диагностика и документация были добавлены, чтобы усилить и объяснить это поведение, я должен предположить, что эти временные правила области видимости допускают более разумный код, чем нет.

person kmdreko    schedule 15.03.2021