Перейти к содержимому

Ошибка Rust E0716: temporary value dropped while borrowed (временное значение)

E0716 значит: ты заимствовал значение, у которого нет имени переменной, а потом продолжил пользоваться этим заимствованием на следующей строке. Такие безымянные значения Rust выбрасывает в конце инструкции, так что заимствование указывало бы в пустоту. Выглядит страшно, но это одна из самых простых ошибок заимствования: обычно достаточно разбить одну строку на две. (Эта страница — часть раздела Ошибки компилятора Rust с объяснениями.)

error[E0716]: temporary value dropped while borrowed
--> src/main.rs:2:13
|
2 | let s = String::from("hi").as_str(); // s borrows a String that has no name
| ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
3 | println!("{s}"); // ❌ that String is already gone
| - borrow later used here
|
help: consider using a `let` binding to create a longer lived value
|
2 ~ let binding = String::from("hi");
3 ~ let s = binding.as_str(); // s borrows a String that has no name
|

Компилятор пишет: временное значение удалено, пока оно ещё заимствовано, — и предлагает создать переменную через let, чтобы значение жило дольше. Вот программа, которая выдаёт эту ошибку:

fn main() {
let s = String::from("hi").as_str(); // s borrows a String that has no name
println!("{s}"); // ❌ that String is already gone
}

Комментарии говорят: s заимствует String без имени, а на следующей строке этой String уже нет.

Временное значение (temporary) — это значение, которое создаётся посреди выражения, но не сохраняется ни в какую переменную. В строке выше String::from("hi") создаёт настоящую String, но ею никто не владеет: для неё нет let имя = .... Rust удаляет (убирает) временное значение в конце инструкции, то есть на ;.

Прочитай три подписи слева направо:

  1. ^^^^ под String::from("hi"): здесь создаётся временная String.
  2. - в конце строки: здесь, на ;, эта String освобождается.
  3. - под s в строке 3: но s (&str, который указывает внутрь этой String) используется здесь.

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

Часть help: в сообщении обычно и есть ответ: создай переменную (rustc называет её binding), чтобы у значения был владелец, который живёт дольше.

Это то же правило, что и в E0597: ссылка никогда не должна жить дольше значения, на которое указывает, иначе она станет висячей ссылкой на освобождённую память. Разница лишь в том, что в E0597 у значения есть имя и оно умирает на }, а в E0716 у значения нет имени и оно умирает на ;.

Использовать временное значение внутри той же инструкции совершенно нормально. println!("{}", String::from("hi").as_str()); компилируется, потому что заимствование заканчивается раньше ;. Ошибка появляется, только когда заимствование нужно на следующей строке. Подробнее в уроках Заимствование и Время жизни.

Есть одно полезное исключение — продление жизни временного значения (temporary lifetime extension). Если поставить & (или &mut) прямо перед временным значением в let, Rust тихо сохранит его живым столько же, сколько живёт переменная. Поэтому эти строки компилируются:

struct Label<'a> {
text: &'a str,
}
fn make_title() -> String {
String::from("Входящие")
}
fn main() {
let greeting = &String::from("привет"); // ✅ продлено: & прямо в let
let queue = &mut vec![1, 2, 3]; // ✅ продлено
queue.push(4);
let label = Label { text: &make_title() }; // ✅ продлено: & прямо в литерале структуры
println!("{greeting} {queue:?} {}", label.text);
}

Вывод:

привет [1, 2, 3, 4] Входящие
▶ Попробуй сам

Продление не проходит сквозь вызов метода или функции. String::from("hi").as_str(), make_title().as_str(), vec![1, 2].iter() и shout(&make_title()) заимствуют временное значение внутри вызова, поэтому оно всё равно умирает на ;, и ты получаешь E0716. Не пытайся запомнить все случаи: если видишь E0716, дай значению имя.

Исправление 1: дай временному значению имя через let

Заголовок раздела «Исправление 1: дай временному значению имя через let»

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

Очень частый случай — разбор строки ввода. read_command() возвращает новую String, .trim() и .split(' ') заимствуют из неё, а Vec<&str> держит эти заимствования:

let parts: Vec<&str> = read_command().trim().split(' ').collect(); // ❌ E0716

Rust указывает на read_command() и говорит, что значение освобождается в конце инструкции. Дай ему имя:

fn read_command() -> String {
// В настоящей программе здесь было бы чтение строки из stdin.
String::from(" добавить молоко яйца \n")
}
fn main() {
let line = read_command(); // теперь String живёт до конца main
let parts: Vec<&str> = line.trim().split(' ').collect();
println!("{parts:?}");
println!("команда: {}, товаров: {}", parts[0], parts.len() - 1);
}

Вывод:

["добавить", "молоко", "яйца"]
команда: добавить, товаров: 2
▶ Попробуй сам

С настоящим вводом ты столкнёшься ровно с этим же: lines.next().unwrap().unwrap().trim().split(' ').collect() для io::stdin().lines() даёт E0716 по той же причине. Сначала вынеси let line = lines.next().unwrap().unwrap(); в отдельную строку. Подробнее в уроке Строки.

Исправление 2: храни владеющие значения вместо ссылок

Заголовок раздела «Исправление 2: храни владеющие значения вместо ссылок»

Выбирай это, когда заимствование должно жить дольше любой переменной, которую ты мог бы создать, например в структуре, которую ты передаёшь дальше, или в Vec, который заполняешь в цикле. let внутри цикла удалялся бы в конце каждого прохода, а это лишь превратит E0716 в E0597.

Вот две сломанные версии. Структура, которая хранит &str из временного значения, и цикл, который кладёт в вектор заимствования новых строк:

let label = Label { text: make_title().as_str() }; // ❌ E0716
let mut names: Vec<&str> = Vec::new();
for name in ["ferris", "corro"] {
names.push(name.to_uppercase().as_str()); // ❌ E0716
}

Исправление — пусть структура и Vec владеют своими строками. Используй String вместо &str и убери .as_str():

struct Label {
text: String, // владеющее поле, время жизни не нужно
}
fn make_title() -> String {
String::from("Входящие")
}
fn main() {
let label = Label { text: make_title() }; // String перемещается в Label
println!("Метка: {}", label.text);
let mut names: Vec<String> = Vec::new();
for name in ["ferris", "corro"] {
names.push(name.to_uppercase()); // Vec владеет каждой новой String
}
println!("{names:?}");
}

Вывод:

Метка: Входящие
["FERRIS", "CORRO"]
▶ Попробуй сам

Новичкам почти всегда проще всего владеющие поля (String, Vec<T>). Структуре с полем &str нужно время жизни, и она никогда не переживёт данные, которые заимствует. Подробнее в уроках Время жизни и Структуры.

Выбирай это, когда временное значение стоит в начале цепочки итераторов. Эти строки создают Vec через vec![...], заимствуют его через .iter() и сохраняют итератор (или его результат) на потом:

let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716
let best = vec![72, 95, 88].iter().max(); // ❌ E0716

&mut в начале не помогает: продление жизни сохраняет итератор, но не Vec, из которого заимствует .iter(). Есть два выхода:

  • Используй into_iter(), чтобы итератор владел Vec (rustc тоже это предлагает). Тогда и &mut не нужен, достаточно let mut.
  • Заверши цепочку владеющим результатом, например .copied(), .cloned(), .sum() или .collect::<Vec<_>>() из обычных чисел, чтобы после ; ничто не заимствовало временное значение.
fn main() {
// Итератор владеет Vec, поэтому никто не заимствует временное значение
let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10);
println!("{:?}", doubled.next());
println!("{:?}", doubled.next());
// .copied() превращает Option<&i32> в Option<i32> — значение без заимствования
let best = vec![72, 95, 88].iter().max().copied();
println!("Лучший: {best:?}");
}

Вывод:

Some(10)
Some(20)
Лучший: Some(95)
▶ Попробуй сам

Если Vec понадобится позже ещё раз, вернись к исправлению 1: let scores = vec![72, 95, 88];, а потом scores.iter().max(). Подробнее в уроке Итераторы.

Блокировка Mutex посреди цепочки. let first = names.lock().unwrap().first(); даёт E0716, потому что охранник блокировки (lock guard) — временное значение. Когда он удаляется, блокировка снимается, так что first не может продолжать указывать внутрь. Исправление: let guard = names.lock().unwrap();, затем guard.first(), или скопируй данные за один раз через .first().cloned(). Подробнее в уроке Общее состояние.

Передача &временного в функцию, которая возвращает ссылку. При fn shout(s: &str) -> &str строка let t = shout(&make_title()); даёт E0716: возвращённый &str заимствует временную String, а продление жизни сквозь вызов функции не работает. Исправление: let title = make_title();, а потом shout(&title).

Возврат из функции. Если написать make_title().as_str() последней строкой функции, которая возвращает ссылку (например, -> &'static str), вместо этого получишь E0515 («cannot return value referencing temporary value»). Исправление: возвращай String.

Проверь себя

1. Что в Rust называется «временным значением»?

2. let s = String::from("hi").as_str();, а затем println!("{s}"); дают E0716. Как обычно это исправляют?

3. Какая строка компилируется благодаря продлению жизни временного значения?

Результат: 0 / 3