String e &str em Rust

Guia completo de String e &str em Rust: criação, manipulação, formatação, conversão e métodos principais com exemplos práticos em português.

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étodoDescriçãoExemplo
push_str(&str)Adiciona um slice ao finals.push_str(" mundo")
push(char)Adiciona um caractere ao finals.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 &strs.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

  1. len() vs chars().count(): O método len() retorna o número de bytes, não de caracteres. Para texto com acentos ou emojis, use chars().count(). Exemplo: "café".len() retorna 5, mas "café".chars().count() retorna 4.

  2. Pré-aloque com with_capacity: Se você sabe o tamanho aproximado da string final, use String::with_capacity(n) para evitar realocações. Isso é especialmente útil em loops.

  3. Evite concatenação com + em loops: Cada uso do operador + pode realocar memória. Prefira push_str() em uma String mutável ou use format! para construções simples.

  4. 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. Use chars() para iterar com segurança.

  5. &str em parâmetros de função: Sempre que possível, aceite &str ao invés de &String nos parâmetros de função. Graças à coerção Deref, tanto String quanto &str podem ser passados.

  6. Use Cow<str> para flexibilidade: Quando uma função às vezes precisa retornar uma string emprestada e às vezes uma string nova, use Cow<str> para evitar clonagens desnecessárias.


Veja Também