Strings em Rust: &str vs String
1. Introdução: O Dilema das Strings em Rust
Em linguagens como C, Python ou JavaScript, strings são tratadas de forma relativamente simples: você cria, modifica e passa adiante sem pensar muito em quem é o "dono" dos dados. Em Rust, a abordagem é radicalmente diferente. A segurança de memória — sem garbage collector — exige que o compilador saiba exatamente onde cada dado está alocado e quem pode acessá-lo.
Isso gera uma dúvida comum entre iniciantes: por que existem dois tipos principais de strings? A resposta está na distinção entre ownership (propriedade) e borrowing (empréstimo). Em Rust, temos:
String: uma string proprietária, alocada no heap, mutável e de tamanho dinâmico.&str(string slice): uma referência imutável para uma sequência de bytes UTF-8, geralmente alocada estaticamente ou emprestada de umaString.
Entender quando usar cada um é essencial para escrever código idiomático e eficiente.
2. String: A String Proprietária e Mutável
String é um tipo que possui ownership sobre os dados. Internamente, é um vetor de bytes (Vec<u8>) que garante codificação UTF-8 válida. Por estar no heap, pode crescer ou encolher dinamicamente.
Criação
let mut s1 = String::new(); // string vazia
let s2 = String::from("Olá, Rust!"); // a partir de um &str
let s3 = "mundo".to_string(); // método to_string()
Mutabilidade e operações comuns
let mut saudacao = String::from("Olá");
saudacao.push_str(", mundo!"); // adiciona &str ao final
saudacao.push('!'); // adiciona um char
saudacao.pop(); // remove último char
println!("{}", saudacao); // "Olá, mundo"
A concatenação pode ser feita com o operador + (que consome o lado esquerdo) ou com format!:
let a = String::from("Hello");
let b = String::from(" World");
let c = a + &b; // a é movido, b é emprestado
// println!("{}", a); // erro: a foi movido
let d = format!("{}{}", c, "!");
println!("{}", d); // "Hello World!"
3. &str: A Fatia de String (String Slice)
&str é uma referência imutável para uma sequência contígua de bytes UTF-8. Ela não possui ownership — apenas "empresta" a visão dos dados.
String literals
Toda string literal em Rust, como "Olá", é do tipo &'static str. Isso significa que ela vive por todo o programa, armazenada diretamente no binário.
let literal: &str = "Rust é seguro";
Criação a partir de String
let proprietaria = String::from("Exemplo");
let slice: &str = &proprietaria[..]; // slice completo
let parte: &str = &proprietaria[0..4]; // "Exem"
Importante: o slice deve respeitar fronteiras de caracteres UTF-8. Fatiar no meio de um caractere multibyte causa pânico em runtime.
4. Comparação Fundamental: Ownership e Mutabilidade
| Característica | String |
&str |
|---|---|---|
| Ownership | Sim (dono dos dados) | Não (apenas referência) |
| Mutabilidade | Sim (com let mut) |
Não (imutável) |
| Alocação | Heap | Stack ou memória estática |
| Tamanho | Dinâmico | Fixo (fatia) |
| Custo de passagem | Move ou clone | Cópia de ponteiro (barato) |
Quando usar cada um
- Parâmetros de função: prefira
&str. Ele aceita tanto&strquanto&String(via deref coercion), dando mais flexibilidade ao chamador.
fn cumprimentar(nome: &str) {
println!("Olá, {}!", nome);
}
cumprimentar("Mundo"); // &str
cumprimentar(&String::from("Rust")); // &String → &str
- Retornos: se a função precisa criar e devolver uma string nova, use
String. Se apenas referencia dados existentes, use&str.
fn nome_completo(primeiro: &str, ultimo: &str) -> String {
format!("{} {}", primeiro, ultimo)
}
5. Conversões Entre String e &str
De &str para String
let s: &str = "exemplo";
let proprietaria1: String = s.to_string();
let proprietaria2: String = String::from(s);
let proprietaria3: String = s.to_owned();
Todas alocam novos dados no heap.
De String para &str
let proprietaria = String::from("dados");
let referencia: &str = &proprietaria; // deref coercion
let explicita: &str = &proprietaria[..]; // slice explícito
Nenhuma alocação extra — apenas uma referência.
Boas práticas
Evite conversões desnecessárias. Se uma função aceita &str, não converta sua String para &str e depois de volta para String. Trabalhe com o tipo adequado desde o início.
6. Strings e UTF-8: Codificação e Indexação
Em Rust, strings são garantidamente UTF-8 válidas. Isso traz implicações importantes:
Indexação direta não funciona
let s = String::from("Olá");
// println!("{}", s[0]); // erro! não é possível indexar String
O motivo: um caractere Unicode pode ocupar mais de 1 byte. s[0] seria ambíguo.
Iteração segura
let texto = "café";
for c in texto.chars() {
println!("{}", c); // c, a, f, é
}
for b in texto.bytes() {
println!("{}", b); // bytes individuais
}
Fatiamento com cuidado
let s = "café";
let fatia = &s[0..3]; // "caf" (3 bytes, 3 chars)
// let erro = &s[3..4]; // pânico! 'é' ocupa 2 bytes (0xC3 0xA9)
Sempre verifique se o índice está em um boundary de caractere.
7. Padrões de Uso no Dia a Dia
Parâmetros de função: prefira &str
fn exibir(mensagem: &str) {
println!("{}", mensagem);
}
Isso aceita literais, &String e slices.
String literals como &'static str
Use para constantes ou mensagens fixas:
const SAUDACAO: &str = "Bem-vindo ao Rust!";
String para dados mutáveis ou de propriedade do struct
struct Usuario {
nome: String, // dono do nome
saudacao: &'static str, // referência estática
}
impl Usuario {
fn novo(nome: &str) -> Self {
Usuario {
nome: nome.to_string(),
saudacao: "Olá",
}
}
}
8. Conclusão e Boas Práticas
Resumo das diferenças
Stringé proprietária, mutável, alocada no heap. Use quando precisar modificar ou armazenar dados.&stré uma referência imutável, sem custo de alocação. Use para parâmetros de função e slices.
Guia rápido
| Situação | Tipo recomendado |
|---|---|
| Parâmetro de função | &str |
| Retorno de função | String (se criar novo dado) |
| Campo de struct | String (exceto literais estáticos) |
| Constante | &'static str |
| Manipulação intensa | String |
Próximos passos
Aprofunde-se em borrowing, slices genéricos (&[T]) e o tipo Cow<str> para cenários onde você pode ou não precisar de ownership.
Referências
- The Rust Programming Language - Chapter 8: Strings — Capítulo oficial do livro sobre strings, com explicações detalhadas sobre UTF-8, indexação e operações.
- Rust by Example - Strings — Exemplos práticos de criação, manipulação e conversão entre
Stringe&str. - Rust Reference - String type — Documentação de referência oficial sobre os tipos textuais em Rust.
- String vs &str in Rust - LogRocket Blog — Artigo técnico comparando ownership, performance e padrões de uso.
- Rust Strings: A Comprehensive Guide - Towards Data Science — Guia abrangente cobrindo codificação, slicing e boas práticas.
- Common Rust Lifetime Misconceptions (including &str) — Esclarece equívocos comuns sobre lifetimes em
&stre outros tipos de referência.