Присвоение параметра метода RefCell локальной переменной приводит к ошибке компиляции

Рассмотрим следующий простой пример:

use std::cell::RefCell;

// Compiles fine
fn good(t: RefCell<String>) -> bool {
    t.borrow().len() == 12
}

// error[E0597]: `t` does not live long enough
fn bad(t: RefCell<String>) -> bool {
    let t = t;
    t.borrow().len() == 12
}

Детская площадка

Функция bad не может скомпилироваться со следующей ошибкой:

error[E0597]: `t` does not live long enough
  --> src/lib.rs:9:5
   |
8  |     let t = t;
   |         - binding `t` declared here
9  |     t.borrow().len() == 12
   |     ^---------
   |     |
   |     borrowed value does not live long enough
   |     a temporary with access to the borrow is created here ...
10 | }
   | -
   | |
   | `t` dropped here while still borrowed
   | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, String>`
   |
   = 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
   |
9  |     let x = t.borrow().len() == 12; x
   |     +++++++                       +++

Мне это кажется крайне странным. Простое переназначение параметра функции локальным переменным приводит к сбою компиляции. Не могли бы вы объяснить, почему?

Честно говоря, я даже не понимаю, почему bad не компилируется, поскольку возвращаемое значение вообще не заимствует.

cdhowie 28.05.2024 03:21
Почему Python в конце концов умрет
Почему Python в конце концов умрет
Последние 20 лет были действительно хорошими для Python. Он прошел путь от "просто языка сценариев" до основного языка, используемого для написания...
5
1
83
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Об этом говорится в Справке:

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

Я не уверен, необходимо ли это для функционирования языка, делает язык более удобным или просто так было изначально реализовано компилятор, но менять сейчас было бы обратно несовместимо. Если кто-то знает больше, прокомментируйте или отредактируйте этот ответ.

Я знал об этом, и иногда это возникает при понимании порядка выпадения. Новым для меня является то, что аргументы функции, судя по всему, отбрасываются еще позже. Об этом почти упоминается здесь, но не совсем. Таким образом, порядок выпадения должен быть:

  • Переменные, созданные внутри функции
  • Временные объекты, созданные в окончательном выражении
  • Аргументы функции

Вот программа, которая это демонстрирует.

struct A(u32);

impl A {
    fn nothing(&self) {}
}

impl Drop for A {
    fn drop(&mut self) {
        println!("drop {}", self.0);
    }
}

fn f(_a1: A) {
    let _a3 = A(3);
    A(2).nothing()
}

fn main() {
    let a = A(1);
    f(a);
}

Он печатает следующее:

drop 3
drop 2
drop 1

В вашем коде последнее выражение создает временный std::cell::Ref<'_, String>. Поскольку Ref имеет поведение Drop — в частности, время жизни содержится в типе, реализующем Drop — в отличие от обычной общей ссылки & требуется, чтобы время жизни было действительным в момент удаления. Поскольку он заимствован из RefCell<String>, Ref необходимо отбросить раньше, чем RefCell. И согласно приведенным выше правилам, это не выполняется, когда RefCell привязано внутри функции.

Обходного пути в сообщении об ошибке всегда должно быть достаточно.

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

Some Name 28.05.2024 04:08

@SomeName Не обязательно. Если временное не одолжено у местного жителя, то это не имеет значения. Например, можно вернуть String::new().len().

cdhowie 28.05.2024 04:11

@SomeName Я думаю, что в целом разумно быть осторожным с типами, которые реализуют Drop (помимо освобождения). Это вызывает только комбинация Drop, заимствования и неясного порядка выпадения, и Drop является самым редким из этих трех.

drewtato 28.05.2024 04:13

@drewtato: «Я не уверен, что это необходимо…» — ваша цитата из «Справочника» объясняет, что «не существует меньшей охватывающей временной области». Какую меньшую охватывающую область, по вашему мнению, мог бы иметь временный объект вместо этого?

eggyal 28.05.2024 04:25

@eggyal Если обходной путь всегда работает, то ржавчина может применить его неявно, и я не могу придумать ни одной ситуации, в которой он не работал бы.

drewtato 28.05.2024 05:18

Другие вопросы по теме