Capítulo 08: Slices, Strings e Vetores Contíguos na Memória
Capítulo 08: Slices, Strings e Vetores Contíguos na Memória
Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance
🗺️ Mapa Conceitual do Layout de Memória: String, &str e Vec<T>
flowchart TD
subgraph STACK_LAYOUT["Memória Stack (Estrutura de Controle em 64-bit)"]
StrStack["String (24 bytes): ptr (0x5000), cap (16), len (8)"]
SliceStack["&str Fat Pointer (16 bytes): ptr (0x5000), len (4)"]
VecStack["Vec<T> (24 bytes): ptr (0x7000), cap (8), len (4)"]
end
subgraph HEAP_LAYOUT["Memória Heap (Buffers Contíguos em Bytes)"]
H_Str["['R', 'U', 'S', 'T', 'C', 'O', 'R', 'E', ...] em 0x5000"]
H_Vec["[0x01, 0x02, 0x03, 0x04, ...] em 0x7000"]
end
StrStack -->|"Dona da Alocação (Capacidade e Gestão)"| H_Str
SliceStack -.->|"Visualização Zero-Copy (&s[0..4])"| H_Str
VecStack -->|"Buffer Contíguo de Dados Dinâmicos"| H_Vec
style STACK_LAYOUT fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style HEAP_LAYOUT fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style SliceStack fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style H_Str fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. O Modelo de Dados Contíguos na Memória
Em programação de sistemas de baixo nível, a afinidade com o hardware e a hierarquia de cache de CPU (L1/L2/L3) são fatores decisivos para a velocidade de processamento. Coleções contíguas na memória garantem que os dados sejam carregados em blocos de linha de cache (Cache Lines de 64 bytes), evitando o terrível desperdício de tempo gerado por Cache Misses.
O Rust oferece três construções contíguas essenciais:
Vec<T>: Um vetor dinâmico homogêneo redimensionável alocado na Heap.String: Um invólucro especializado em torno deVec<u8>com a garantia estrita de codificação UTF-8 válida.- Slices (
&[T]e&str): Referências para uma sequência contígua de elementos, sem possuir o ownership da coleção.
⚙️ 2. A Dualidade: String vs &str
Compreender a diferença exata entre String e &str é o rito de passagem para todo engenheiro de software em Rust:
| Dimensão Técnica | String (Heap-Allocated) |
&str (String Slice / Fat Pointer) |
|---|---|---|
| Ownership | Proprietária do buffer na Heap | Empréstimo sem ownership |
| Layout na Stack | 24 bytes (ptr + len + capacity) | 16 bytes (ptr + len) |
| Mutabilidade | Redimensionável (.push_str(), .clear()) |
Visualização imutável de bytes |
| Origem do Dado | Alocação dinâmica em tempo de execução | Pode apontar para Heap, Stack ou binário ('static) |
| Uso em Parâmetros | Quando a função precisa assumir posse | Recomendado para 99% das funções que só leem texto |
Exemplo de Fatiamento de Strings (String Slicing):
let mut frase = String::from("SISTEMAS_RUST");
// Fatia que aponta para os primeiros 8 bytes da String existente
let fatia_sistemas: &str = &frase[0..8]; // "SISTEMAS"
println!("Fatia extraída sem alocar nova memória: '{}'", fatia_sistemas);
🔤 3. Por que o Rust Proíbe texto[0]? (Segurança UTF-8)
Em linguagens como C ou Python, indexar strings diretamente por número (s[0]) é comum. No entanto, o Rust rejeita a sintaxe texto[i] em tempo de compilação:
- Cada caractere em UTF-8 pode ocupar de 1 a 4 bytes.
- Letras latinas simples ocupam 1 byte (
'A'), enquanto caracteres acentuados ocupam 2 bytes ('á') e emojis ocupam 4 bytes ('🦀'). - Permitir indexação numérica por byte levaria a quebras acidentais de caracteres no meio de seus bytes, corrompendo o texto.
A Maneira Correta de Percorrer Strings:
let saudacao = "Olá, 🦀!";
// 1. Percorrendo caracteres Unicode individuais:
for c in saudacao.chars() {
print!("[{}] ", c);
}
// Saída: [O] [l] [á] [,] [ ] [🦀] [!]
// 2. Percorrendo bytes brutos:
println!("\nTotal de bytes brutos na memória: {}", saudacao.len());
📊 4. Vetores Dinâmicos: Vec<T> e Gerenciamento de Capacidade
O Vec<T> gerencia internamente um ponteiro para a Heap, o comprimento atual (len) e o espaço reservado total (capacity):
// Criação com pré-alocação de capacidade (Evita realocações custosas na Heap)
let mut sensores: Vec<u32> = Vec::with_capacity(100);
for id in 1..=5 {
sensores.push(id * 10);
}
println!("Comprimento: {} | Capacidade: {}", sensores.len(), sensores.capacity());
// Liberando espaço não utilizado para economizar RAM
sensores.shrink_to_fit();
println!("Capacidade após shrink: {}", sensores.capacity());
⚖️ 5. Fatias de Vetores e Arrays (&[T])
Uma fatia &[T] é agnóstica em relação ao local onde os dados estão alocados: ela funciona identicamente sobre vetores da Heap (Vec<T>) ou arrays estáticos da Stack ([T; N]):
fn somar_bloco_memoria(fatia: &[i32]) -> i32 {
fatia.iter().sum()
}
fn main() {
let array_stack: [i32; 4] = [10, 20, 30, 40];
let vetor_heap: Vec<i32> = vec![1, 2, 3, 4, 5];
// A mesma função atende ambos os tipos sem nenhuma cópia!
let soma1 = somar_bloco_memoria(&array_stack[1..3]); // Fatia de array da stack
let soma2 = somar_bloco_memoria(&vetor_heap[0..2]); // Fatia de vetor da heap
println!("Soma 1: {} | Soma 2: {}", soma1, soma2);
}
⚠️ 6. Armadilhas Comuns e Mensagens do Compilador
- Tentativa de Indexação Direta de Strings:
let s = String::from("teste"); let c = s[0]; // ERRO: `String` cannot be indexed by `{integer}`Correção: Use
s.chars().nth(0).unwrap()ou faça um slice de bytes caso conheça os limites UTF-8:&s[0..1]. - Panic por Indexação Fora dos Limites UTF-8:
Tentar fatiar uma string no meio de um caractere multibyte (ex:
&"Olá"[0..2]) causará um panic em runtime:byte index 2 is not a char boundary.
🛡️ 7. Boas Práticas e Performance de Alta Vazão
- Use
Vec::with_capacity(n)Sempre que Souber o Tamanho Aproximado: Inserir 1.000 itens em umVec::new()força o alocador a redimensionar e copiar o buffer na Heap diversas vezes. Comwith_capacity(1000), a alocação ocorre uma única vez. - Assinaturas de Funções com
&stre&[T]: Nunca declare parâmetros como&Stringou&Vec<T>. O uso de&stre&[T]aceita muito mais tipos de entrada via Deref Coercion e torna suas bibliotecas universalmente flexíveis.
📋 8. Cheat-Sheet: Resumo Executivo
// String proprietária vs Slice
let s = String::from("Rust");
let fatia: &str = &s[0..2]; // "Ru"
// Vetor pré-alocado
let mut v = Vec::with_capacity(64);
v.push(10);
// Assinatura canônica e idiomática
fn processar(texto: &str, lista: &[u8]) { /* Zero Copy */ }