Rust: Um guia completo sobre strings no Rust
Última atualização: 2026-08-26
O Rust não tem apenas um, mas dois tipos de string — porque a programação de sistemas exige um controle preciso sobre a memória.
O manuseio de strings no Rust é motivo de confusão para muitos iniciantes: por que existem dois tipos — String e &str? Em que eles diferem do char* em C? Esta lição vai ajudar você a entender tudo isso.
1. O que você vai aprender
- A diferença entre literais de string
&stre strings alocadas no heapString - Criação, adição, concatenação e formatação
String - Segmentação de strings e acesso indexado
- Métodos comuns para strings (len, contains, replace, trim)
- O impacto da codificação UTF-8 nas operações com strings
2. A história de uma bibliotecária
(1) Problema: Os dados JSON são muito longos para caberem
Maria é uma bibliotecária que está desenvolvendo um pequeno sistema de busca de livros para gerenciar 5.000 livros.
- O comprimento do título de cada livro varia; o mais longo tem 200 caracteres
- No início, ela usava strings de comprimento fixo para armazenar títulos de livros (como
char[50]em C). - Acontece que “Cem Anos de Solidão” cabe, mas “Como Fazer um Mapa Completo de um Universo Infinito” fica cortado.
- Depois de mudar para
char*, me deparei com outro problema de liberação de memória — esqueci de chamarfreeem um ponto, e o sistema travou.
“Seria ótimo se houvesse strings que gerenciassem a memória automaticamente...” Mais tarde, ela encontrou a resposta no Rust.
(2) A solução para strings no Rust
fn main() {
// &str: String Literals, Present in the binary at compile time
let title_book1: &str = "One Hundred Years of Solitude";
// String: Allocated on the heap, Variable length, Automatic Memory Management
let mut title_book2 = String::from("How to Make a Complete Map of an Infinite Universe");
title_book2.push_str(" (Second Edition)"); // Additional Content
println!("Book 1: {} (Length: {} Bytes)", title_book1, title_book1.len());
println!("Book 2: {} (Length: {} Bytes)", title_book2, title_book2.len());
// String Automatically released when leaving the scope, No manual free required
}
O
Stringdo Rust é semelhante às strings do Java — ele gerencia a memória automaticamente; o&stré semelhante aoconst char*do C — referências somente para leitura. Juntos, eles alcançam um equilíbrio entre desempenho e flexibilidade.
3. A diferença entre &str e String
(1) Modelo de memória
graph LR
subgraph "&str (String Literals)"
S1_ptr[ptr ──→ ...rust...]
S1_len[len: 4]
end
subgraph "String (Heap Allocation)"
S2_ptr[ptr ──→ Rust is...]
S2_len[len: 10]
S2_cap[cap: 16]
end
subgraph "Storage Location"
STACK[Stack: &str<br>Read-Only Borrow]
HEAP[Heap: String<br>Ownership]
end
S1_ptr -.->|Determined at compile time| STACK
S2_ptr -.->|Runtime Allocation| HEAP
| Características | &str |
String |
|---|---|---|
| Proprietário | Acesso somente para leitura | Propriedade total |
| Mutabilidade | Imutável | Pode ser acrescentado/modificado |
| Local de armazenamento | Segmento de dados somente leitura (em tempo de compilação) | Heap (alocado em tempo de execução) |
| Método de criação | Literal "hello" |
String::from() ou .to_string() |
| Casos de uso | Parâmetros de string somente leitura | É necessário modificar ou assumir a propriedade da string |
(2) Referência rápida aos métodos comuns de strings
| Método | Tipo de retorno | Descrição | Exemplo |
|---|---|---|---|
push_str(&str) |
() |
Trecho adicional da sequência de caracteres | s.push_str("abc") |
push(char) |
() |
Adicionar um único caractere | s.push('!') |
len() |
usize |
Comprimento do byte de retorno | s.len() |
is_empty() |
bool |
Está vazio? | s.is_empty() |
trim() |
&str |
Remover espaços à esquerda e à direita | s.trim() |
contains(&str) |
bool |
Se a subsequência está incluída | s.contains("rust") |
replace(&str, &str) |
String |
Substituir todas as ocorrências | s.replace("a", "b") |
| Dividir por delimitador | split(char) |
Split |
s.split(',') |
| Converter para letras minúsculas | |||
| Letras maiúsculas | to_uppercase() |
String |
Alternar para maiúsculas |
chars() |
Chars |
Iterar por caractere | s.chars() |
bytes() |
Bytes |
Iterar por bytes | s.bytes() |
| Limpar string | |||
capacity() |
usize |
Voltar para Capacidade do buffer | s.capacity() |
(3) Método de criação
// &str Create
let s1: &str = "hello, world"; // String Literals
let s2: &str = r"raw string\n"; // Original string(Do not escape)
// String Create
let s3 = String::from("hello");
let s4 = "world".to_string();
let s5 = format!("{}-{}", s3, s4); // Format and Create
let s6: String = "hi".into(); // Through Type Conversion
(4) Referência rápida aos métodos de conversão de strings
| Tipo de origem | Tipo de destino | Método de conversão | Sem sobrecarga |
|---|---|---|---|
| Alocar memória do heap | |||
| Alocar memória do heap | |||
| Alocar memória do heap | |||
String |
&str |
&s ou s.as_str() |
Custo zero (empréstimo) |
String |
&str |
&s[..] |
Custo zero (fatia) |
&str |
&[u8] |
"hello".as_bytes() |
Custo zero (empréstimo) |
String |
Vec<u8> |
s.into_bytes() |
Sem custos adicionais (transferência de propriedade) |
Vec<u8> |
String |
String::from_utf8(v) |
Sem sobrecarga (transferência de propriedade; verificação UTF-8 necessária) |
4. Operações comuns com strings
▶ Exemplo 1: Criação e modificação de strings (Dificuldade ⭐)
// ============================================
// String Creation, Append and Concatenation
// ============================================
fn main() {
// Create
let mut s = String::from("Hello");
// Add
s.push_str(", "); // Append a string slice
s.push('R'); // Add a single character
s.push_str("ust!");
println!("After the addition: {}", s);
// Concatenation (+ operator consumes the left-hand side String)
let s1 = String::from("Hello, ");
let s2 = String::from("World!");
let s3 = s1 + &s2; // s1 Moved, It can't be used anymore.
println!("After concatenation: {}", s3);
// format! macro (Does not consume any variables)
let a = String::from("tic");
let b = String::from("tac");
let c = String::from("toe");
let result = format!("{}-{}-{}", a, b, c);
println!("format Results: {}", result);
}
Resultado:
After the addition: Hello, Rust!
After concatenation: Hello, World!
format Result: tic-tac-toe
O operador
+consome (move) oStringà sua esquerda; portanto, os1não pode mais ser usado após a concatenação. Oformat!não consome nenhuma variável; a propriedade de todas as variáveis é mantida.
▶ Exemplo 2: Segmentação e indexação de strings (Dificuldade ⭐⭐)
// ============================================
// String Slicing -- Note UTF-8 Boundary!
// ============================================
fn main() {
let s = String::from("Rust🦀Code");
// Get the length of a string (Number of bytes, Not the number of characters)
println!("Byte Length: {}", s.len()); // 11 (4 Byte ASCII + 4 Byte UTF-8 emoji + 4 Byte ASCII)
// String Slicing (Index by Byte, Must be at a character boundary)
let slice1 = &s[0..4]; // "Rust" (first 4 bytes fall exactly on a character boundary)
println!("First 4 Byte Slice: {}", slice1);
// &s[0..5] will panic! (Because bytes 4-5 are in the middle of the emoji)
// Iterate by character count
for c in s.chars() {
print!("{} ", c);
}
println!();
// Iterate by byte
for b in s.bytes() {
print!("{:02x} ", b);
}
println!();
}
Resultado:
Byte Length: 11
First 4 Byte Slice: Rust
R u s t 🦀 C o d e
52 75 73 74 f0 9f a6 80 43 6f 64 65
As strings do Rust não suportam acesso direto por índice (como
s[0]), pois os caracteres UTF-8 têm comprimento variável. É necessário percorrer os caracteres usando.chars()ou utilizar fatias de bytes. As fatias devem estar alinhadas com os limites dos caracteres; caso contrário, ocorrerá uma falha em tempo de execução.
▶ Exemplo 3: Métodos comuns para strings (Dificuldade ⭐⭐)
// ============================================
// Common String Queries, Replace, Cutting Methods
// ============================================
fn main() {
let s = " Hello, Rust World! ";
// Remove leading and trailing spaces
println!("trim: '{}'", s.trim());
// Includes a check
println!("contains 'Rust': {}", s.contains("Rust"));
// Replace
let replaced = s.replace("Rust", "Go");
println!("replace: '{}'", replaced);
// Split
let parts: Vec<&str> = "apple,banana,cherry".split(',').collect();
println!("split: {:?}", parts);
// Case Conversion
let lower = "HELLO".to_lowercase();
let upper = "world".to_uppercase();
println!("lower: {}, upper: {}", lower, upper);
}
Resultado:
trim: 'Hello, Rust World!'
contains 'Rust': true
replace: ' Hello, Go World! '
split: ["apple", "banana", "cherry"]
lower: hello, upper: WORLD
trim()esplit()retornam&strsem criar uma nova string.replace()retorna umStringtotalmente novo. Leve em consideração a sobrecarga de desempenho ao fazer modificações.
▶ Exemplo 4: Iteração por strings UTF-8 e verificações de limites (Dificuldade ⭐⭐⭐)
// ============================================
// UTF-8 Bytes in a string, Character and Grapheme Clusters
// ============================================
fn main() {
let s = "Rust🦀Code";
println!("String: {}", s);
println!("Byte Length: {}", s.len());
println!("Number of characters: {}", s.chars().count());
println!("\n--- Character-by-character traversal ---");
for (i, c) in s.chars().enumerate() {
println!("Character {}: '{}' (Unicode: U+{:04X})", i, c, c as u32);
}
println!("\n--- Traverse byte by byte ---");
for (i, b) in s.bytes().enumerate() {
print!("{:02x} ", b);
if (i + 1) % 8 == 0 { println!(); }
}
println!();
println!("\n--- Safe Slices ---");
let ascii_part = &s[0..4];
println!("ASCII Part &s[0..4]: '{}'", ascii_part);
let emoji_start = 4;
let emoji_end = emoji_start + 4;
let emoji_part = &s[emoji_start..emoji_end];
println!("Emoji Part &s[{}..{}]: '{}'", emoji_start, emoji_end, emoji_part);
let code_start = emoji_end;
let code_end = code_start + 4;
let code_part = &s[code_start..code_end];
println!("Code Part &s[{}..{}]: '{}'", code_start, code_end, code_part);
let combined = format!("{}{}{}", ascii_part, emoji_part, code_part);
println!("Concatenated Reconstruction: {}", combined);
}
Resultado:
String: Rust🦀Code
Byte Length: 12
Number of characters: 8
--- Character-by-character traversal ---
Character 0: 'R' (Unicode: U+0052)
Character 1: 'u' (Unicode: U+0075)
Character 2: 's' (Unicode: U+0073)
Character 3: 't' (Unicode: U+0074)
Character 4: '🦀' (Unicode: U+1F980)
Character 5: 'C' (Unicode: U+0043)
Character 6: 'o' (Unicode: U+006F)
Character 7: 'd' (Unicode: U+0064)
Character 8: 'e' (Unicode: U+0065)
--- Traverse byte by byte ---
52 75 73 74 f0 9f a6 80 43 6f 64 65
--- Safe Slices ---
ASCII Part &s[0..4]: 'Rust'
Emoji Part &s[4..8]: '🦀'
Code Part &s[8..12]: 'Code'
Concatenated Reconstruction: Rust🦀Code
No UTF-8, os caracteres ASCII ocupam 1 byte e os emojis, 4 bytes. A divisão deve ocorrer nos limites entre caracteres; caso contrário, ocorrerá um erro grave em tempo de execução. Use
.chars().enumerate()para processar os caracteres um por um com segurança.
▶ Exemplo 5: Análise e formatação prática de strings (Dificuldade: ⭐⭐)
// ============================================
// String Parsing, Combined Use of Formatting and Linking
// ============================================
fn main() {
let raw_data = "name=Alice;age=30;city=Beijing";
println!("Raw Data: {}", raw_data);
println!("\n--- Parsing Key-Value Pairs ---");
let pairs: Vec<&str> = raw_data.split(';').collect();
for pair in pairs {
let parts: Vec<&str> = pair.split('=').collect();
if parts.len() == 2 {
let key = parts[0].trim();
let value = parts[1].trim();
println!("{} => {}", key, value);
}
}
println!("\n--- Construct a New String ---");
let name = "Alice";
let age = 30;
let city = "Beijing";
let summary = format!("{}, {} years old, From {}", name, age, city);
println!("Abstract: {}", summary);
let mut report = String::from("Personnel Report\n");
report.push_str(&format!("Name: {}\n", name));
report.push_str(&format!("Age: {}\n", age));
report.push_str(&format!("City: {}\n", city));
report.push_str("--- End ---");
println!("\n{}", report);
let csv_line = ["Alice", "30", "Beijing"].join(",");
println!("\nCSV Format: {}", csv_line);
}
Resultado:
Raw Data: name=Alice;age=30;city=Beijing
--- Parsing Key-Value Pairs ---
name => Alice
age => 30
city => Beijing
--- Construct a New String ---
Abstract: Alice, 30 years old, From Beijing
Personnel Report
Name: Alice
Age: 30
City: Beijing
--- End ---
CSV Format: Alice,30,Beijing
split()+collect()são os padrões básicos para a análise de texto estruturado;format!é adequado para concatenação única;push_str()é adequado para a construção gradual de textos longos;join()é adequado para conectar matrizes com delimitadores.
❓ Perguntas Frequentes
P: Por que o Rust tem dois tipos de strings? R: Para equilibrar propriedade e desempenho.
&stré um empréstimo somente leitura (sem sobrecarga), enquantoStringé uma string mutável de propriedade (com sobrecarga de alocação na pilha). Usar&strpara parâmetros de função oferece maior flexibilidade, já queStringpode ser automaticamente convertido para&str.
P: Como se faz a conversão entre String e &str? R: Para converter &str em String, use
.to_string()ouString::from(); para converter String em &str, use&sous.as_str(). Todas as conversões têm custo zero (elas simplesmente representam a memória sob uma perspectiva diferente).
P: Por que s[0] não consegue acessar o primeiro caractere? R: Porque o UTF-8 é uma codificação de comprimento variável.
s[0]acessa o primeiro byte, que não é necessariamente um caractere completo. O Rust foi projetado para ser mais seguro: ele prefere gerar um erro em tempo de compilação a permitir um comportamento inesperado em tempo de execução.
P: Devo usar + ou format! para concatenar strings? R: Use + para concatenar um número pequeno de strings e format! para vários segmentos.
+consome a String à esquerda e reutiliza seu buffer (para alto desempenho), enquantoformat!não consome a variável, mas cria uma String totalmente nova. Use + para dois ou três segmentos e format! para mais de três.
P: As strings podem ser modificadas? O que pode ser modificado, &str ou String? R: Apenas
Stringpode ser modificado (após declararmut, você pode usar métodos como.push_str(),.push(),.insert(), etc.).&stré uma referência somente leitura; os dados aos quais ela aponta não podem ser modificados. Se você precisar modificar uma string dentro de uma função, o tipo do parâmetro deve ser&mut String.
📖 Resumo
- O Rust possui dois tipos de strings:
&str(empréstimo somente para leitura) eString(propriedade) Stringé uma string UTF-8 redimensionável, alocada na pilha, cuja memória é automaticamente desalocada quando sai do escopo&stré um trecho de string que aponta para uma sequência contígua de bytes UTF-8 válidos- Devido à codificação UTF-8, as cadeias de caracteres não permitem acesso direto por índice; é necessário usar
.chars()ou fatias de bytes. - Métodos comuns:
.len(),.trim(),.contains(),.replace(),.split() - Quando os parâmetros de uma função aceitam strings, use
&strsempre que possível para garantir o máximo de flexibilidade
📝 Exercícios
- Dificuldade ⭐: Crie um
Stringe, em seguida, chame os operadores.push_str(),.push(),+eformat!em sequência, comparando os resultados das quatro abordagens. - Dificuldade ⭐⭐: Escreva uma função
fn first_word(s: &str) -> &strque retorne a primeira palavra de uma sequência de caracteres (separada por espaços). Chame-a usando&streString, respectivamente. - Dificuldade ⭐⭐⭐: Explore problemas de limites do UTF-8 — crie uma string contendo o emoji 🦀
"Rust🦀", tente dividi-la com&s[0..5]e&s[0..6]e observe qual delas funciona e qual causa uma falha.