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 uma String.

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 &str quanto &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