O que são String e &str
Em Rust, existem dois tipos principais para representar texto: String e &str. A String é um tipo owned (proprietário) — ela é alocada no heap, pode crescer ou encolher, e é dona dos seus dados. Já o &str é uma referência imutável a uma sequência de bytes UTF-8 válida, também chamada de string slice. Literais de texto como "olá" são do tipo &str e ficam embutidos no binário.
A distinção entre String e &str é um dos conceitos mais importantes em Rust. Use String quando precisar de posse sobre o texto (por exemplo, para armazená-lo em uma struct ou retorná-lo de uma função). Use &str quando só precisar ler o texto, sem modificá-lo. Ambos os tipos são sempre UTF-8 válidos, o que garante segurança ao lidar com texto internacional.
Criando Strings
Existem diversas formas de criar valores String e &str:
fn main() {
// &str — literal de string (armazenado no binário)
let literal: &str = "Olá, mundo!";
// String a partir de um literal
let s1 = String::from("Olá, mundo!");
let s2 = "Olá, mundo!".to_string();
let s3: String = "Olá, mundo!".into();
// String vazia
let vazia = String::new();
// String com capacidade pré-alocada
let mut com_capacidade = String::with_capacity(100);
com_capacidade.push_str("Texto inicial");
// A partir de caracteres
let de_chars: String = vec!['R', 'u', 's', 't'].into_iter().collect();
// A partir de bytes (pode falhar se não for UTF-8 válido)
let bytes = vec![79, 108, 195, 161];
let de_bytes = String::from_utf8(bytes).expect("UTF-8 inválido");
assert_eq!(de_bytes, "Olá");
// Repetindo uma string
let repetida = "ha".repeat(3); // "hahaha"
// Formatação com format!
let nome = "Rust";
let formatada = format!("Eu amo {}!", nome);
println!("{literal} | {s1} | {de_chars} | {repetida} | {formatada}");
}
Tabela de Métodos Principais
| Método | Descrição | Exemplo |
|---|---|---|
push_str(&str) | Adiciona um slice ao final | s.push_str(" mundo") |
push(char) | Adiciona um caractere ao final | s.push('!') |
len() | Retorna o tamanho em bytes | "café".len() → 5 |
chars().count() | Retorna o número de caracteres | "café".chars().count() → 4 |
is_empty() | Verifica se está vazia | "".is_empty() → true |
contains(&str) | Verifica se contém um trecho | "Rust".contains("us") → true |
starts_with(&str) | Verifica prefixo | "Rust".starts_with("Ru") → true |
ends_with(&str) | Verifica sufixo | "Rust".ends_with("st") → true |
replace(&str, &str) | Substitui todas as ocorrências | "aabb".replace("a", "x") → "xxbb" |
replacen(&str, &str, n) | Substitui as primeiras n ocorrências | "aaa".replacen("a", "b", 2) → "bba" |
trim() | Remove espaços no início e fim | " oi ".trim() → "oi" |
trim_start() | Remove espaços no início | " oi ".trim_start() → "oi " |
trim_end() | Remove espaços no final | " oi ".trim_end() → " oi" |
to_uppercase() | Converte para maiúsculas | "rust".to_uppercase() → "RUST" |
to_lowercase() | Converte para minúsculas | "RUST".to_lowercase() → "rust" |
split(&str) | Divide em partes | "a,b,c".split(",") |
splitn(n, &str) | Divide em no máximo n partes | "a,b,c".splitn(2, ",") |
lines() | Itera sobre linhas | "a\nb".lines() |
find(&str) | Posição da primeira ocorrência (em bytes) | "Rust".find("st") → Some(2) |
as_str() | Converte String para &str | s.as_str() |
as_bytes() | Retorna como slice de bytes | "abc".as_bytes() |
chars() | Iterador de caracteres | "olá".chars() |
bytes() | Iterador de bytes | "abc".bytes() |
Exemplos Práticos
1. Concatenando Strings
fn main() {
// Usando push_str (mais eficiente para múltiplas operações)
let mut resultado = String::from("Olá");
resultado.push_str(", ");
resultado.push_str("mundo!");
println!("{resultado}"); // "Olá, mundo!"
// Usando o operador + (consome o lado esquerdo)
let saudacao = String::from("Olá");
let nome = String::from("Rust");
let mensagem = saudacao + ", " + &nome + "!";
// Nota: saudacao foi movida, não pode mais ser usada
println!("{mensagem}"); // "Olá, Rust!"
// Usando format! (não consome nenhum valor)
let cidade = "São Paulo";
let estado = "SP";
let local = format!("{cidade} - {estado}");
println!("{local}"); // "São Paulo - SP"
// Juntando um iterador com join
let partes = vec!["Rust", "é", "incrível"];
let frase = partes.join(" ");
println!("{frase}"); // "Rust é incrível"
}
2. Processando Texto Linha por Linha
fn contar_palavras(texto: &str) -> Vec<(&str, usize)> {
use std::collections::HashMap;
let mut contagem: HashMap<&str, usize> = HashMap::new();
for palavra in texto.split_whitespace() {
*contagem.entry(palavra).or_insert(0) += 1;
}
let mut resultado: Vec<_> = contagem.into_iter().collect();
resultado.sort_by(|a, b| b.1.cmp(&a.1));
resultado
}
fn main() {
let texto = "rust é rápido rust é seguro rust é produtivo";
let contagem = contar_palavras(texto);
for (palavra, qtd) in &contagem {
println!("{palavra}: {qtd}");
}
// rust: 3
// é: 3
// rápido: 1
// seguro: 1
// produtivo: 1
}
3. Validando e Limpando Input do Usuário
fn limpar_email(input: &str) -> Option<String> {
let email = input.trim().to_lowercase();
if email.is_empty() {
return None;
}
if !email.contains('@') || !email.contains('.') {
return None;
}
let partes: Vec<&str> = email.split('@').collect();
if partes.len() != 2 || partes[0].is_empty() || partes[1].is_empty() {
return None;
}
Some(email)
}
fn main() {
let entradas = vec![
" Usuario@Email.COM ",
"invalido",
"",
"valido@exemplo.com.br",
];
for entrada in entradas {
match limpar_email(entrada) {
Some(email) => println!("Válido: {email}"),
None => println!("Inválido: '{entrada}'"),
}
}
}
4. Trabalhando com UTF-8 e Caracteres Especiais
fn main() {
let texto = "Café é bom! 🦀";
// len() retorna bytes, não caracteres!
println!("Bytes: {}", texto.len()); // 19
println!("Caracteres: {}", texto.chars().count()); // 15
// Iterando sobre caracteres
for (i, c) in texto.chars().enumerate() {
if !c.is_ascii() {
println!("Caractere não-ASCII na posição {i}: '{c}'");
}
}
// Fatiando com segurança (por índice de byte)
// CUIDADO: fatiar no meio de um caractere multi-byte causa panic!
let cafe = &texto[0..5]; // "Café" (é ocupa 2 bytes)
println!("{cafe}");
// Forma segura de pegar os primeiros N caracteres
let primeiros_4: String = texto.chars().take(4).collect();
println!("{primeiros_4}"); // "Café"
// Verificando se uma string é ASCII
println!("É ASCII? {}", "hello".is_ascii()); // true
println!("É ASCII? {}", "café".is_ascii()); // false
}
5. Conversões entre Tipos
fn main() {
// Número para String
let n: i32 = 42;
let s = n.to_string();
let s2 = format!("{n:06}"); // "000042" (padding com zeros)
// String para número
let numero: i32 = "42".parse().expect("Não é um número");
let pi: f64 = "3.14".parse().expect("Não é um float");
// String para &str
let minha_string = String::from("olá");
let slice: &str = &minha_string; // coerção automática
let slice2: &str = minha_string.as_str();
// &str para String
let meu_slice: &str = "olá";
let string: String = meu_slice.to_string();
let string2: String = String::from(meu_slice);
// Vec<u8> para String
let bytes: Vec<u8> = vec![82, 117, 115, 116];
let texto = String::from_utf8(bytes).unwrap();
assert_eq!(texto, "Rust");
// String para Vec<u8>
let bytes_de_volta: Vec<u8> = texto.into_bytes();
println!("{s} {s2} {numero} {pi} {slice} {string} {}",
String::from_utf8(bytes_de_volta).unwrap());
}
Dicas de Performance e Armadilhas
len()vschars().count(): O métodolen()retorna o número de bytes, não de caracteres. Para texto com acentos ou emojis, usechars().count(). Exemplo:"café".len()retorna5, mas"café".chars().count()retorna4.Pré-aloque com
with_capacity: Se você sabe o tamanho aproximado da string final, useString::with_capacity(n)para evitar realocações. Isso é especialmente útil em loops.Evite concatenação com
+em loops: Cada uso do operador+pode realocar memória. Prefirapush_str()em umaStringmutável ou useformat!para construções simples.Fatiar strings UTF-8 com cuidado: Indexar com
[i..j]funciona em bytes, não caracteres. Fatiar no meio de um caractere multi-byte causa panic em tempo de execução. Usechars()para iterar com segurança.&strem parâmetros de função: Sempre que possível, aceite&strao invés de&Stringnos parâmetros de função. Graças à coerçãoDeref, tantoStringquanto&strpodem ser passados.Use
Cow<str>para flexibilidade: Quando uma função às vezes precisa retornar uma string emprestada e às vezes uma string nova, useCow<str>para evitar clonagens desnecessárias.
Veja Também
- Formatar Strings em Rust — receitas práticas de formatação
- Dividir Strings em Rust — padrões para split e parsing
- Vec<T>: Vetor Dinâmico — para quando precisar de coleções de strings
- Cow<T>: Clone on Write — para evitar alocações desnecessárias com strings
- Slices (&[T]) — conceito similar aplicado a sequências genéricas
- Iterator Trait — para processar caracteres e linhas de texto
- Cheatsheet Rust — referência rápida de sintaxe
- Documentação oficial — std::string::String
- Documentação oficial — str