Por que &String, &Vec e &Box são más práticas em Rust?

Treinador Online de Python para Iniciantes

Aprenda Python facilmente sem sobrecarga teórica. Resolva tarefas práticas com verificação automática e escreva código diretamente no navegador.

Start Course

Introdução: A armadilha em que todos os iniciantes em Rust caem

\n\n

Você está aprendendo Rust. Você já entendeu o conceito de posse e empréstimo. Você está escrevendo sua primeira função séria e pensa: "Ah, para não mover a string, vou passar uma referência!". E você escreve:

\n\n
fn print_string(s: &String) {\n    println!("{}", s);\n}
\n\n

O compilador está feliz. O código funciona. Você está feliz. Mas o problema é: um desenvolvedor Rust experiente, ao ver seu código, vai franzir a testa. Por quê? Afinal, tudo funciona!

\n\n

Esta pergunta é uma das mais populares no StackOverflow. Milhares de desenvolvedores tropeçam neste detalhe aparentemente insignificante. Hoje vamos entender por que &String, &Vec e &Box na assinatura de uma função são uma bandeira vermelha e como escrever corretamente.

\n\n

Problema nº1: Restrição implícita de tipos (Type Coercion)

\n\n

Rust possui um sistema poderoso de coerção automática de tipos, mas apenas em uma direção. Isso é chamado de Deref Coercion (coerção por desreferenciamento).

\n\n

A ideia é simples: se você tem uma referência a String, Rust automaticamente pode transformá-la em uma referência a &str. O mesmo acontece com &Vec<T> → &[T] e &Box<T> → &T.

\n\n

Veja a diferença:

\n\n
// Ruim: aceita apenas &String\nfn bad(s: &String) {\n    println!("{}", s);\n}\n\n// Bom: aceita &str\nfn good(s: &str) {\n    println!("{}", s);\n}\n\nfn main() {\n    let owned = String::from("Olá, mundo!");\n    let slice = "Olá, slice!";\n    \n    bad(&owned); // Funciona\n    // bad(slice); // ERRO! slice é &str, não &String\n    \n    good(&owned); // Funciona (coerção automática)\n    good(slice);   // Funciona (já é &str)\n}
\n\n

Viu? A função good aceita qualquer string: tanto a proprietária String, quanto o literal de string &str, e até mesmo parte de uma string. A função bad aceita apenas String. Isso é uma restrição desnecessária.

\n\n

Exemplo prático: busca de substring

\n\n
// Busca limitada\nfn find_in_string(haystack: &String, needle: &str) -> bool {\n    haystack.contains(needle)\n}\n\n// Busca universal\nfn find_in_str(haystack: &str, needle: &str) -> bool {\n    haystack.contains(needle)\n}\n\nfn main() {\n    let my_string = String::from("Rust é incrível");\n    let my_str = "Rust é incrível";\n    \n    // Funciona apenas com String\n    find_in_string(&my_string, "Rust");\n    // find_in_str(my_str, "Rust"); // Erro de compilação!\n    \n    // Funciona com ambos\n    find_in_str(&my_string, "Rust");\n    find_in_str(my_str, "Rust");\n}
\n\n

Problema nº2: Desempenho e indireções desnecessárias

\n\n

Vamos olhar "por baixo do capô". O que é String na memória? É uma estrutura com três campos:

\n
    \n
  • Ponteiro para os dados no heap
  • \n
  • Comprimento da string
  • \n
  • Capacidade (capacity)
  • \n
\n\n

Quando você passa &String, você passa uma referência para esta estrutura. Para acessar os dados, o compilador precisa:

\n
    \n
  1. Desreferenciar &String → obter a estrutura String
  2. \n
  3. Da estrutura, pegar o ponteiro para os dados no heap
  4. \n
  5. Desreferenciar este ponteiro → obter os bytes da string
  6. \n
\n\n

Quando você passa &str, você passa um fat pointer (ponteiro gordo), que consiste em apenas dois campos:

\n
    \n
  • Ponteiro para os dados
  • \n
  • Comprimento
  • \n
\n\n

O compilador precisa dar apenas um passo — desreferenciar o ponteiro. Menos indireção significa código mais rápido. A diferença é microscópica, mas em loops críticos ou ao passar para

Blogs

Book Recommendations