Rust: Tempos de vida no Rust
Última atualização: 2026-08-26
O Lifetime é o mecanismo que o Rust utiliza para garantir que “as referências nunca fiquem pendentes” — ele atribui uma “data de validade” a cada referência.
Na verdade, você já está usando durações — todo o código das Lições 9 e 10 possui durações; o compilador simplesmente as “adivinhou” para você. Esta lição ensina quando é necessário especificar manualmente as durações.
1. O que você vai aprender
- Por que é necessário um ciclo de vida? — Para evitar referências pendentes
- Sintaxe das anotações de ciclo de vida:
'a,'b - Tempos de vida nas assinaturas de funções — Tempos de vida dos parâmetros e dos valores de retorno
- Regras de omissão de vida — Quando o compilador pode fazer inferências automaticamente?
- Anotações de ciclo de vida em estruturas
'staticCiclo de vida — Referências permanentes
2. Diagramas conceituais
flowchart LR
subgraph "Function Signature fn longest<'a>"
X["x: &'a str"] --> RET["Return Value: &'a str"]
Y["y: &'a str"] --> RET
end
NOTE["'a = the shorter of x and y's lifetimes"] -.-> RET
3. Uma história sobre a caçada a um rato
(1) Frustração: O rato fugiu, mas eu continuo observando
Tom é um biólogo que está usando um rastreador para registrar a localização de ratos:
The tracker locks onto the mouse A → Mouse A ran into the cave → The tracker is still pointing toward the entrance.
- O rastreador (referência) aponta para o Mouse A (dados)
- O mouse A fugiu (os dados foram destruídos)
- O traçador ainda está apontando para esse local (referência pendente — perigoso!)
“Seria ótimo se o rastreador tivesse uma etiqueta que dissesse: ‘Válido até que o rato A saia do seu buraco’...”
(2) Soluções para o ciclo de vida do Rust
// Life Cycle Annotation 'a: Tell the compiler "the validity period of the return value does not exceed that of parameter x's validity period"
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let string1 = String::from("Long Strings");
let result;
{
let string2 = String::from("short");
result = longest(&string1, &string2); // ✅ string2 and string1 are both still alive
println!("The longer one is: {}", result);
} // string2 Destroyed here
// println!("{}", result); // ❌ If you print here, result references string2 which is destroyed
} // string1 Destroyed here
Os períodos de validade são como etiquetas de “data de validade” anexadas aos rastreadores. O compilador usa essas etiquetas para determinar se uma referência ainda está dentro de seu período de validade. Neste exemplo, o período de validade de
resultnão pode exceder o destring2.
4. Como funciona o ciclo de vida
(1) Toda referência tem um ciclo de vida
fn main() {
let x: i32 = 10; // x Its life cycle begins here
let r: &i32 = &x; // r Its life cycle begins here
println!("r: {}", r); // Usage r
} // r whose lifecycle ends first, then x ends
(2) Por que é necessário anotar os ciclos de vida?
Quando uma função retorna uma referência, o compilador precisa saber de qual parâmetro essa referência provém:
graph TB
A[Functions Return References] --> B{Which parameter does the return value point to?}
B --> C[Pointer Parameters1 → The Lifecycle of Return Values ≤ Parameters1]
B --> D[Pointer Parameters2 → The Lifecycle of Return Values ≤ Parameters2]
B --> E[Point to one of the two → Must be labeled: Find the intersection]
C --> F[No annotation required (the compiler can infer)]
D --> F
E --> G[Manual annotation is required 'a]
| Cenário | Exemplo | É necessário fazer uma anotação? |
|---|---|---|
| Referência de entrada única | fn first(x: &str) -> &str |
Não é necessário (regra de omissão) |
| Várias referências de entrada | fn longest(x: &str, y: &str) -> &str |
Obrigatório (o compilador não sabe qual escolher) |
| Não retorna uma referência | fn len(x: &str) -> usize |
Não é necessário |
| Estruturas que contêm referências | struct S<'a> { r: &'a i32 } |
Obrigatório |
(3) Referência rápida para locais de anotação do ciclo de vida
| Localização | Sintaxe | Descrição |
|---|---|---|
| Definição da função | fn foo<'a>(x: &'a str) -> &'a str |
Parâmetros e valores de retorno |
| Definição de estrutura | struct S<'a> { r: &'a str } |
Uma estrutura não pode permanecer ativa após o fim de sua referência |
| bloco impl | impl<'a> S<'a> |
Declarando o ciclo de vida ao implementar um método |
| restrição de característica | where T: 'a |
Os tipos de restrição não podem conter referências de curta duração |
| Anotação estática | &'static str |
Válida durante toda a execução do programa |
5. Anotação do ciclo de vida
(1) Sintaxe
// 'a is the name of the lifecycle parameter (using 'a, 'b, 'c)
// Pronunciation: Regarding the lifecycle 'a, both x and y need to be alive for at least 'a
fn function<'a>(x: &'a str, y: &'a str) -> &'a str {
// The lifecycle of return values = the shorter of x and y
}
(2) Regras de omissão do ciclo de vida
O compilador pode inferir automaticamente os tempos de vida (sem que você precise especificá-los) nos três casos a seguir:
| Regra | Significado |
|---|---|
| Cada referência de entrada tem seu próprio ciclo de vida | fn foo(x: &str) atribui automaticamente um ciclo de vida a x |
| Apenas uma referência de entrada | O tempo de vida da referência de saída = o tempo de vida da referência de entrada |
| Várias entradas, sendo que uma delas é &self ou &mut self | O tempo de vida da referência de saída é &self |
// No annotation required: there is only one input reference
fn first_word(s: &str) -> &str { &s[..] }
// Needs annotation: two input references
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
6. Exemplos de ciclo de vida
▶ Exemplo 1: Por que precisamos de um ciclo de vida? (Dificuldade ⭐⭐⭐)
// ============================================
// What happens if there are no lifecycle annotations?
// ============================================
// This function has two input references, the return value could be any one of these
// ❌ If not annotated, a compilation error will occur: expected lifetime parameter
// fn longest_wrong(x: &str, y: &str) -> &str {
// if x.len() > y.len() { x } else { y }
// }
// ✅ Correct Version: Lifecycle Annotation
fn longest<'a>(x: &'a str, y: &'a str) -> &'a str {
if x.len() > y.len() { x } else { y }
}
fn main() {
let s1 = String::from("hello");
let result;
{
let s2 = String::from("hi");
result = longest(&s1, &s2); // result lifecycle = s2 lifecycle (the shorter one)
println!("The longer one is: {}", result);
} // s2 destroyed, result invalid
// println!("{}", result); // ❌ The compiler will prevent: result's lifecycle has ended
}
Resultado:
The longer one is: hello
O tempo de vida
'aé definido como o menor entrexey. Se o tempo de vida do valor de retorno fosse definido como o maior, ele poderia ainda estar em uso depois que o menor já tivesse sido destruído — o que é exatamente o que o compilador está tentando evitar.
▶ Exemplo 2: Parâmetros para diferentes ciclos de vida (Dificuldade ⭐⭐⭐)
// ============================================
// The two parameters have different lifecycles.
// ============================================
// Two Lifecycles: 'a corresponds to x, 'b corresponds to y
// The return value depends only on x, so use 'a
fn choose_first<'a, 'b>(x: &'a str, y: &'b str) -> &'a str {
x // Return only x, so the lifecycle only needs 'a
}
fn main() {
let x = String::from("Live to be a hundred years old");
let result;
{
let y = String::from("Short-lived ghost");
result = choose_first(&x, &y); // ✅ result Depends solely on x
println!("result: {}", result);
} // y destroyed, but result does not depend on y, so no problem
println!("Remains valid even after leaving the inner layer: {}", result); // ✅ x Still Alive
}
Resultado:
result: Live to be a hundred years old
Remains valid even after leaving the inner layer: Live to be a hundred years old
Quando o valor de retorno depende exclusivamente de um determinado parâmetro, basta anotar o tempo de vida desse parâmetro. Mesmo que o parâmetro
choose_firstemytenha vida curta, isso não afeta o resultado — pois o valor de retorno não utiliza absolutamente nenhum dado dey.
▶ Exemplo 3: Ciclo de vida em estruturas (Dificuldade ⭐⭐⭐⭐)
// ============================================
// Storing references in a structure -- the lifecycle must be specified.
// ============================================
struct Excerpt<'a> {
content: &'a str, // Structures Borrow External Strings
}
impl<'a> Excerpt<'a> {
fn length(&self) -> usize {
self.content.len()
}
fn announce(&self, announcement: &str) -> &str {
println!("Announcement: {}", announcement);
self.content // Returning a Reference to the Interior of a Structure
}
}
fn main() {
let novel = String::from("Call me Ishmael. Some years ago...");
let first_sentence = novel.split('.').next().expect("Can't find the period");
let excerpt = Excerpt {
content: &first_sentence,
};
println!("Excerpt: {}", excerpt.content);
println!("Length: {}", excerpt.length());
println!("Announcement of Results: {}", excerpt.announce("Welcome to this article"));
} // excerpt must be destroyed before novel (content depends on novel)
Resultado:
Excerpt: Call me Ishmael
Length: 15
Announcement of Results: Call me Ishmael
Quando uma estrutura contém referências, é necessário especificar um parâmetro de tempo de vida
<'a>no nome da estrutura. Isso indica ao compilador que o tempo de vida de uma instância da estrutura não pode exceder o tempo de vida dos dados aos quais ela faz referência.
▶ Exemplo 4: Ciclo de vida “estático” (Dificuldade ⭐⭐⭐)
// ============================================
// 'static: the reference remains valid throughout the program's execution.
// ============================================
fn main() {
// A string literal is 'static
let s: &'static str = "I exist throughout the entire lifecycle of a program";
println!("{}", s);
// 'static Constraints: T must not contain any non-static references
fn print_static<T: 'static>(val: &T) {
println!("{:?}", std::ptr::from_ref(val));
}
let num: i32 = 42;
print_static(&num); // ✅ i32 excludes references, complies with 'static
// let msg = String::from("temp");
// let r = &msg;
// print_static(&r); // ❌ r references non-'static msg
}
Resultado:
I exist throughout the entire lifecycle of a program
0x...(Memory Address)
'staticé a duração mais longa no Rust — os dados permanecem existentes desde o início do programa até o seu término. Literais de string têm uma duração'static. Observação:'staticdeve ser interpretado como “esta referência é válida para sempre”, e não como “esses dados sobrevivem até o fim”.
▶ Exemplo 5: Exercício abrangente — Analisador de texto (Dificuldade ⭐⭐⭐)
// ============================================
// Life Cycle in Practice: Zero-Copy Text Analysis
// ============================================
struct TextAnalyzer<'a> {
text: &'a str,
}
impl<'a> TextAnalyzer<'a> {
fn new(text: &'a str) -> Self {
TextAnalyzer { text }
}
fn word_count(&self) -> usize {
self.text.split_whitespace().count()
}
fn longest_word(&self) -> &'a str {
self.text
.split_whitespace()
.max_by_key(|w| w.len())
.unwrap_or("")
}
fn first_n_words(&self, n: usize) -> Vec<&'a str> {
self.text.split_whitespace().take(n).collect()
}
fn line_count(&self) -> usize {
self.text.lines().count()
}
fn char_count(&self) -> usize {
self.text.chars().count()
}
fn summary(&self) -> String {
format!(
"Character: {}, Words: {}, Number of lines: {}, Longest Word: '{}'",
self.char_count(),
self.word_count(),
self.line_count(),
self.longest_word()
)
}
}
fn highlight_word<'a>(text: &'a str, word: &str) -> Vec<&'a str> {
text.split_whitespace()
.filter(|w| w.contains(word))
.collect()
}
fn main() {
let article = "Rust is a systems programming language \
that runs blazingly fast and prevents segfaults. \
Rust guarantees memory safety and thread safety.";
let analyzer = TextAnalyzer::new(article);
println!("=== Text Analysis ===");
println!("{}", analyzer.summary());
let top3 = analyzer.first_n_words(3);
println!("\nFirst 3 words: {:?}", top3);
let rust_mentions = highlight_word(article, "Rust");
println!("Includes 'Rust' the word: {:?}", rust_mentions);
let paragraph = "First line.\nSecond line.\nThird line.";
let p_analyzer = TextAnalyzer::new(paragraph);
println!("\nParagraph Analysis: {}", p_analyzer.summary());
}
Resultado:
=== Text Analysis ===
Character: 124, Words: 18, Number of lines: 1, Longest Word: 'blazingly'
First 3 words: ["Rust", "is", "a"]
Includes 'Rust' the word: ["Rust", "Rust"]
Paragraph Analysis: Character: 39, Words: 6, Number of lines: 3, Longest Word: 'Second'
TextAnalyzerMantém uma referência ao texto original durante todo o seu ciclo de vida<'a>— todos os métodos de análise são do tipo “zero-copy”.longest_wordO valor retornado&'a straponta para trechos de palavras no texto original e não resulta em nenhuma alocação de memória.
❓ Perguntas Frequentes
P: O tempo de vida é um conceito de tempo de execução? R: Não! O tempo de vida é um conceito exclusivamente de tempo de compilação. O compilador usa anotações de tempo de vida para verificar a validade das referências, e o executável gerado não contém nenhuma informação sobre tempo de vida — sem nenhuma sobrecarga no tempo de execução.
P: O que significa o “a” em “a”? R: É apenas um nome; é costume usar a, b, c e assim por diante. “a” é pronunciado como “tick A” ou “lifecycle A”. Você poderia muito bem escrever isso como
'my_lifetime, mas usar uma única letra é a convenção na comunidade Rust.
P: Por que as anotações de tempo de vida não são visíveis na maior parte do código em Rust? R: Por causa da regra de omissão de tempo de vida, que permite que o compilador as infira por nós. Na maioria dos casos (referências de entrada única ou métodos &self), o compilador consegue inferi-las corretamente, então você não precisa escrevê-las.
P: Qual é a relação entre o tempo de vida da referência de entrada 'a' e o valor de retorno 'a'? R: Eles assumem a interseção. O tempo de vida do valor de retorno não pode exceder o tempo de vida de nenhuma das referências de entrada. Se x tem um tempo de vida de 10 segundos e y tem um tempo de vida de 5 segundos, então 'a' tem um tempo de vida de, no máximo, 5 segundos — isso é conhecido como o “menor dos dois tempos de vida”.
P: Quando preciso definir um ciclo de vida por conta própria? R: Existem apenas três casos: quando uma função recebe várias referências de entrada e retorna uma referência, quando uma estrutura contém uma referência e quando se implementa um trait que restringe tipos associados. Em todos os outros casos, o compilador cuidará disso para você.
📖 Resumo
- Lifetimes são um mecanismo de tempo de compilação que o Rust utiliza para garantir que as referências permaneçam válidas.
- Use a sintaxe
'apara vincular os períodos de validade das referências de entrada e saída - As regras de omissão do ciclo de vida eliminam a necessidade de anotação manual em mais de 80% dos cenários
- Ao armazenar referências em uma estrutura, é necessário especificar um parâmetro de tempo de vida.
'statictem o ciclo de vida mais longo (toda a duração da execução do programa)- Sem sobrecarga de execução ao longo de todo o seu ciclo de vida — ele desaparece assim que é compilado.
📝 Exercícios
- Dificuldade ⭐: Leia um trecho de código existente em Rust (como o exemplo da Lição 9) e identifique quais funções evitam a anotação manual graças à regra de elisão de tempo de vida.
- Dificuldade ⭐⭐⭐: Escreva uma função
fn shortest<'a>(x: &'a str, y: &'a str) -> &'a strque retorne a menor de duas cadeias de caracteres. Emmain, crie variáveis com tempos de vida diferentes para chamá-la. - Dificuldade ⭐⭐⭐⭐: Defina uma estrutura
Book<'a>que contenha um campotitledo tipo&'a str. Escreva um blocoimplque implemente um métodofn first_word(&self) -> &strque retorne a primeira palavra do título. Demonstre seu uso emmain.