Ошибка 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 use3 | 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 удаляет (убирает) временное значение в конце инструкции, то есть на ;.
Прочитай три подписи слева направо:
^^^^подString::from("hi"): здесь создаётся временнаяString.-в конце строки: здесь, на;, этаStringосвобождается.-подsв строке 3: ноs(&str, который указывает внутрь этойString) используется здесь.
Представь официанта, который записывает заказ на стикер и выбрасывает стикер, как только отходит от стола. Если ты скажешь другу «заказ на том стикере», друг ничего не найдёт. Стикер нужно оставить себе или записать заказ туда, где он сохранится.
Часть help: в сообщении обычно и есть ответ: создай переменную (rustc называет её binding), чтобы у значения был владелец, который живёт дольше.
Почему Rust так делает
Заголовок раздела «Почему Rust так делает»Это то же правило, что и в 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(); // ❌ E0716Rust указывает на 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 нужно время жизни, и она никогда не переживёт данные, которые заимствует. Подробнее в уроках Время жизни и Структуры.
Исправление 3: перестрой цепочку
Заголовок раздела «Исправление 3: перестрой цепочку»Выбирай это, когда временное значение стоит в начале цепочки итераторов. Эти строки создают Vec через vec![...], заимствуют его через .iter() и сохраняют итератор (или его результат) на потом:
let doubled = &mut vec![1, 2, 3].iter().map(|n| n * 10); // ❌ E0716let 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.
Связанные ошибки
Заголовок раздела «Связанные ошибки»- E0597: borrowed value does not live long enough: та же проблема для значения, у которого есть имя и которое удаляется на
}. - E0515: cannot return reference to local variable: возврат из функции заимствования локального или временного значения.
- E0106: missing lifetime specifier: полю структуры вроде
text: &strнужно время жизни. Часто это знак, что лучше взятьString. - E0502: mutable borrow while immutably borrowed: ещё одна ошибка «заимствование всё ещё используется», но об изменении данных, а не об их удалении.
- E0382: borrow of moved value: после перехода на владеющие значения следи, чтобы не использовать значение после перемещения.
- Все ошибки компилятора с объяснениями
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Что в Rust называется «временным значением»?
2. let s = String::from("hi").as_str();, а затем println!("{s}"); дают E0716. Как обычно это исправляют?
3. Какая строка компилируется благодаря продлению жизни временного значения?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- E0716 — список кодов ошибок Rust (на английском): официальное объяснение этой ошибки с исправлением через
let. - Ссылки и заимствование — книга «Язык программирования Rust» (перевод на русский): правила заимствования, в том числе почему ссылки никогда не бывают висячими.
- Temporary scopes — The Rust Reference (на английском): точные правила того, когда удаляется временное значение.
- Temporary lifetime extension — The Rust Reference (на английском): какие формы
letпродлевают жизнь временного значения, с примерами. - Валидация ссылок посредством сроков жизни — книга «Язык программирования Rust» (перевод на русский): как Rust сравнивает, сколько живёт значение и сколько используется ссылка.