Rust Admin

Boas Práticas de String vs &str em Rust: Entendendo as Duas Formas de Texto para Times Ágeis Já leu

String vs &str em Rust: Entendendo as Duas Formas de Texto O que são String e &str? Em Rust, temos duas formas principais de representar texto: e . A diferença fundamental está em como a memória é gerenciada e onde os dados são armazenados. é um tipo alocado dinamicamente no heap, você é responsável por seu gerenciamento, enquanto é uma referência a uma sequência de bytes UTF-8 válida, geralmente alocada na stack ou na seção de dados do binário. Pense em como um vetor de caracteres que você pode modificar livremente — tem capacidade, comprimento e proprietário. Já é uma visão imutável sobre esses dados: um "empréstimo" que aponta para um local de memória sem poder alterar o conteúdo. Essa distinção é central na filosofia de segurança de memória do Rust. Diferenças de Performance e Memória consome mais memória porque armazena três informações no stack: um ponteiro para os dados no heap, a capacidade e o comprimento atual. A cada

String vs &str em Rust: Entendendo as Duas Formas de Texto

O que são String e &str?

Em Rust, temos duas formas principais de representar texto: String e &str. A diferença fundamental está em como a memória é gerenciada e onde os dados são armazenados. String é um tipo alocado dinamicamente no heap, você é responsável por seu gerenciamento, enquanto &str é uma referência a uma sequência de bytes UTF-8 válida, geralmente alocada na stack ou na seção de dados do binário.

Pense em String como um vetor de caracteres que você pode modificar livremente — tem capacidade, comprimento e proprietário. Já &str é uma visão imutável sobre esses dados: um "empréstimo" que aponta para um local de memória sem poder alterar o conteúdo. Essa distinção é central na filosofia de segurança de memória do Rust.

fn main() {
    // String: alocado no heap, mutável, proprietário
    let mut s1 = String::from("Olá");
    s1.push_str(", Mundo!");
    println!("{}", s1); // "Olá, Mundo!"

    // &str: referência imutável, literais são na stack/binário
    let s2: &str = "Olá, Rust!";
    println!("{}", s2); // "Olá, Rust!"

    // &str pode vir de uma String
    let s3 = String::from("Teste");
    let referencia: &str = &s3;
    println!("{}", referencia); // "Teste"
}

Diferenças de Performance e Memória

String consome mais memória porque armazena três informações no stack: um ponteiro para os dados no heap, a capacidade e o comprimento atual. A cada alocação, pode haver fragmentação de memória. &str, por sua vez, é apenas um ponteiro (64 bits) e um comprimento (64 bits) — 16 bytes no total em arquiteturas de 64 bits — tornando-a muito mais leve para passar entre funções.

Quando você trabalha com strings literais como "Olá", o compilador as coloca como dados imutáveis no binário (seção .rodata), e &str simplesmente aponta para lá. Não há alocação dinâmica. Use &str como padrão em parâmetros de funções; use String apenas quando você realmente precisa possuir e modificar o texto.

// Não faça isto (ineficiente):
fn processar_texto(texto: String) {
    println!("{}", texto);
}

// Faça isto:
fn processar_texto(texto: &str) {
    println!("{}", texto);
}

fn main() {
    let s = String::from("Dados importantes");
    processar_texto(&s); // passa &str, não String
    processar_texto("Literal direto"); // funciona naturalmente
}

Conversão e Coerção

Rust permite coerção automática de String para &str em muitos contextos. Quando você passa uma String para uma função que espera &str, o compilador automaticamente converte. Isso é uma das características mais úteis da linguagem — você possui uma String, mas empresta apenas uma visão quando necessário.

Para converter explicitamente, use &string[..] ou &string (quando esperado &str). Para ir na direção oposta, de &str para String, use String::from(), .to_string() ou .to_owned(). Entender essas conversões é crucial para não lutar contra o borrow checker do Rust.

fn saudacao(nome: &str) -> String {
    format!("Olá, {}!", nome)
}

fn main() {
    // Coerção automática: String vira &str
    let nome = String::from("Alice");
    let msg = saudacao(&nome); // &nome é &str

    // Conversão explícita
    let slice: &str = &nome[0..5]; // "&str" a partir de parte de String
    println!("{}", slice); // "Alice"

    // &str para String
    let literai: &str = "Bob";
    let proprietario: String = literai.to_string();
    println!("{}", proprietario); // "Bob"
}

Quando Usar Cada Uma

Use &str em assinaturas de funções como argumento padrão — é ergonômico e eficiente. Use String quando você precisa possuir, modificar ou construir texto dinamicamente, como em loops que concatenam dados ou ao ler de entrada do usuário. Em estruturas de dados, String é a escolha padrão quando você precisa armazenar texto; &str é usada para referências que vivem enquanto seus dados subjacentes existem.

Não tenha medo de ter String em suas estruturas. A cláusula de tempo de vida em &str frequentemente torna o código mais complexo sem benefício real. Reserve &str para quando realmente está apenas emprestando texto, não para quando é responsável por sua existência.

#[derive(Debug)]
struct Pessoa {
    nome: String, // proprietária dos dados
    email: String,
}

impl Pessoa {
    fn new(nome: &str, email: &str) -> Self {
        Pessoa {
            nome: nome.to_string(),
            email: email.to_string(),
        }
    }

    fn apresentar(&self) -> String {
        format!("Sou {} ({})", self.nome, self.email)
    }
}

fn main() {
    let pessoa = Pessoa::new("Carlos", "carlos@email.com");
    println!("{}", pessoa.apresentar());
}

Conclusão

Os três pontos essenciais: primeiro, String é alocado dinamicamente e mutável, enquanto &str é uma referência imutável e leve; segundo, a coerção automática permite passar String onde &str é esperado, facilitando a vida; terceiro, use &str em parâmetros de funções e String em propriedades de estruturas e quando você controla o ciclo de vida do dado.

Dominar essa distinção é fundamental em Rust. Investir tempo compreendendo String vs &str elimina frustrações futuras com o borrow checker e resulta em código mais eficiente.

Referências


Artigos relacionados