Pular para o conteúdo

Erro E0716 do Rust: temporary value dropped while borrowed (valor temporário)

E0716 significa: você pegou emprestado um valor que não tem nome de variável e continuou usando esse empréstimo em uma linha seguinte. O Rust joga fora esses valores sem nome no fim da instrução, então o empréstimo apontaria para o nada. A mensagem assusta, mas é um dos erros de borrowing (empréstimo) mais fáceis de corrigir: normalmente basta dividir uma linha em duas. (Esta página faz parte de Erros do compilador do Rust explicados.)

error[E0716]: temporary value dropped while borrowed
--> src/main.rs:2:13
|
2 | let s = String::from("hi").as_str(); // s pega emprestada uma String sem nome
| ^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement
| |
| creates a temporary value which is freed while still in use
3 | println!("{s}"); // ❌ essa String já não existe
| - 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 pega emprestada uma String sem nome
|

O compilador diz que um valor temporário foi descartado enquanto ainda estava emprestado (temporary value dropped while borrowed) e sugere criar uma variável com let para que ele viva mais. Este é o programa que causa o erro:

fn main() {
let s = String::from("hi").as_str(); // s pega emprestada uma String sem nome
println!("{s}"); // ❌ essa String já não existe
}

Um temporário é um valor criado no meio de uma expressão, mas que nunca é guardado em uma variável. Na linha acima, String::from("hi") cria uma String de verdade, mas ninguém é dono dela: não existe um let nome = ... para ela. O Rust descarta (libera) um temporário no fim da instrução, ou seja, no ;.

Leia os três rótulos da esquerda para a direita:

  1. ^^^^ embaixo de String::from("hi"): aqui é criada uma String temporária (creates a temporary value).
  2. - no fim da linha: aqui, no ;, essa String é liberada (freed at the end of this statement).
  3. - embaixo de s na linha 3: mas s (um &str que aponta para dentro dessa String) ainda é usado aqui (borrow later used here).

Pense em um garçom que anota o seu pedido em um papelzinho e joga o papel fora assim que sai da mesa. Se você diz a um amigo “o pedido está naquele papel”, seu amigo não encontra nada. Você precisa guardar o papel você mesmo, ou anotar o pedido em algum lugar que fique.

A parte help: da mensagem costuma ser a resposta: crie uma variável (o rustc a chama de binding) para que o valor tenha um dono que viva mais tempo.

É a mesma regra do E0597: uma referência nunca pode viver mais que o valor para o qual aponta, senão ela vira uma referência pendente para uma memória já liberada. A única diferença é que, no E0597, o valor tem nome e morre em um }, enquanto no E0716 o valor não tem nome e morre no ;.

Usar um temporário dentro da mesma instrução não tem problema nenhum. println!("{}", String::from("hi").as_str()); compila, porque o empréstimo termina antes do ;. O erro só aparece quando o empréstimo ainda é necessário em uma linha seguinte. Veja Quando um borrow termina? e Lifetimes.

Existe uma exceção útil, chamada temporary lifetime extension (extensão do lifetime de temporários). Se você coloca & (ou &mut) diretamente na frente do temporário em um let, o Rust mantém o temporário vivo, sem alarde, pelo mesmo tempo que a variável. Por isso estas linhas compilam:

struct Label<'a> {
text: &'a str,
}
fn make_title() -> String {
String::from("Inbox")
}
fn main() {
let greeting = &String::from("hi"); // ✅ estendido: & direto no let
let queue = &mut vec![1, 2, 3]; // ✅ estendido
queue.push(4);
let label = Label { text: &make_title() }; // ✅ estendido: & direto em um literal de struct
println!("{greeting} {queue:?} {}", label.text);
}

Saída:

hi [1, 2, 3, 4] Inbox
▶ Experimente você mesmo

A extensão não atravessa uma chamada de método nem uma chamada de função. String::from("hi").as_str(), make_title().as_str(), vec![1, 2].iter() e shout(&make_title()) pegam o temporário emprestado dentro de uma chamada, então o temporário continua morrendo no ; e você recebe o E0716. Não tente decorar todos os casos: se aparecer o E0716, dê um nome ao valor.

Escolha esta quase sempre. É o que o compilador sugere, e é a resposta certa para a maioria dos E0716. Guarde o valor em uma variável própria primeiro e depois pegue emprestado dessa variável na linha seguinte.

Um caso muito comum é dividir uma linha de entrada. read_command() devolve uma String nova, .trim() e .split(' ') pegam emprestado dela, e o Vec<&str> guarda esses empréstimos:

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

O Rust aponta para read_command() e diz que ele é liberado no fim da instrução. Dê um nome a ele:

fn read_command() -> String {
// Num programa de verdade, isto leria uma linha do stdin.
String::from(" adicionar leite ovos \n")
}
fn main() {
let line = read_command(); // agora a String vive até o fim de main
let parts: Vec<&str> = line.trim().split(' ').collect();
println!("{parts:?}");
println!("comando: {}, itens: {}", parts[0], parts.len() - 1);
}

Saída:

["adicionar", "leite", "ovos"]
comando: adicionar, itens: 2
▶ Experimente você mesmo

Você vai esbarrar exatamente nisso com entrada de verdade também: lines.next().unwrap().unwrap().trim().split(' ').collect() em io::stdin().lines() dá E0716 pelo mesmo motivo. Coloque let line = lines.next().unwrap().unwrap(); em uma linha separada antes. Veja Strings.

Correção 2: Guarde valores próprios em vez de referências

Seção intitulada “Correção 2: Guarde valores próprios em vez de referências”

Escolha esta quando o empréstimo precisa viver mais que qualquer variável que você poderia criar, por exemplo em uma struct que você passa adiante ou em um Vec que você preenche dentro de um laço. Um let dentro de um laço seria descartado no fim de cada volta, o que só transforma o E0716 em E0597.

Aqui estão duas versões com erro. Uma struct que guarda um &str de um temporário, e um laço que coloca no vetor empréstimos de strings novas:

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
}

A correção é deixar a struct e o Vec serem donos das suas strings. Use String em vez de &str e tire o .as_str():

struct Label {
text: String, // próprio, então não precisa de lifetime
}
fn make_title() -> String {
String::from("Inbox")
}
fn main() {
let label = Label { text: make_title() }; // a String é movida para dentro do Label
println!("Rótulo: {}", label.text);
let mut names: Vec<String> = Vec::new();
for name in ["ferris", "corro"] {
names.push(name.to_uppercase()); // o Vec é dono de cada String nova
}
println!("{names:?}");
}

Saída:

Rótulo: Inbox
["FERRIS", "CORRO"]
▶ Experimente você mesmo

Para quem está começando, campos próprios (String, Vec<T>) são quase sempre a escolha mais fácil. Uma struct com um campo &str precisa de um lifetime e nunca pode viver mais que os dados que pegou emprestado. Veja Structs que guardam referências e Structs.

Escolha esta quando o temporário é o começo de uma cadeia de iteradores. Estas linhas criam um Vec com vec![...], o pegam emprestado com .iter() e guardam o iterador (ou o resultado dele) para depois:

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

O &mut na frente não ajuda: a extensão de lifetime mantém vivo o iterador, mas não o Vec que .iter() pega emprestado. Há duas saídas:

  • Use into_iter() para que o iterador seja dono do Vec (o rustc também sugere isso). Aí você nem precisa do &mut, só de let mut.
  • Termine a cadeia com um resultado próprio, como .copied(), .cloned(), .sum() ou .collect::<Vec<_>>() de números simples, para que nada pegue o temporário emprestado depois do ;.
fn main() {
// O iterador é dono do Vec, então nada pega um temporário emprestado
let mut doubled = vec![1, 2, 3].into_iter().map(|n| n * 10);
println!("{:?}", doubled.next());
println!("{:?}", doubled.next());
// .copied() transforma Option<&i32> em Option<i32>, um valor sem empréstimo
let best = vec![72, 95, 88].iter().max().copied();
println!("Melhor: {best:?}");
}

Saída:

Some(10)
Some(20)
Melhor: Some(95)
▶ Experimente você mesmo

Se você precisar do Vec de novo mais tarde, volte para a Correção 1: let scores = vec![72, 95, 88]; e depois scores.iter().max(). Veja Iteradores.

Travando um Mutex no meio de uma cadeia. let first = names.lock().unwrap().first(); dá E0716, porque o guard da trava é um temporário. Quando ele é descartado, a trava é liberada, então first não pode continuar apontando para dentro dela. Correção: let guard = names.lock().unwrap(); e depois guard.first(), ou copie os dados para fora de uma vez com .first().cloned(). Veja Estado compartilhado.

Passando &temporário para uma função que devolve uma referência. Com fn shout(s: &str) -> &str, a linha let t = shout(&make_title()); dá E0716: o &str devolvido pega emprestada a String temporária, e a extensão de lifetime não atravessa uma chamada de função. Correção: let title = make_title(); e depois shout(&title).

Devolvendo de uma função. Se você escreve make_title().as_str() como a última linha de uma função que devolve uma referência (como -> &'static str), recebe o E0515 (“cannot return value referencing temporary value”, não é possível devolver um valor que referencia um valor temporário) no lugar. Correção: devolva a String.

Teste o que aprendeu

1. No Rust, o que é um "temporário"?

2. let s = String::from("hi").as_str(); seguido de println!("{s}"); dá E0716. Qual é a correção mais comum?

3. Qual linha compila graças à extensão do lifetime de temporários?

Pontuação: 0 / 3