Algoritmos e Estruturas de Dados em Rust: Guia Completo para Iniciantes
Rust é uma linguagem de programação que combina o alto desempenho de C/C++ com a segurança de memória garantida em tempo de compilação. Para desenvolvedores iniciantes, estudar algoritmos e estruturas de dados em Rust não é apenas uma forma de dominar conceitos fundamentais da Ciência da Computação, mas também uma oportunidade de entender características únicas da linguagem: propriedade (ownership), empréstimo (borrowing) e tempos de vida (lifetimes).
Neste artigo, abordaremos as principais estruturas de dados e algoritmos que todo desenvolvedor deve conhecer. Todos os exemplos de código estão escritos em Rust e prontos para execução. Começaremos com as estruturas básicas fornecidas pela biblioteca padrão e, em seguida, passaremos para a implementação de algoritmos clássicos.
1. Estruturas de Dados Principais em Rust
A biblioteca padrão do Rust (std) fornece um rico conjunto de estruturas de dados prontas. Vamos analisar as três mais importantes.
Vetores (Vec)
Vec é um array dinâmico que pode alterar seu tamanho. É análogo ao ArrayList em Java ou à lista em Python. Os vetores garantem a disposição contígua dos elementos na memória, tornando o acesso por índice muito rápido (O(1)).
fn main() { // Criando um vetor vazio let mut numbers: Vec<i32> = Vec::new(); // Adicionando elementos numbers.push(10); numbers.push(20); numbers.push(30); // Acesso por índice let first = numbers[0]; // 10 // Iteração for num in &numbers { println!("{}", num); } // Macro vec! let fruits = vec!["apple", "banana", "cherry"]; println!("Segunda fruta: {}", fruits[1]); // banana}Mapas Hash (HashMap)
HashMap é uma estrutura de dados que armazena pares chave-valor. Ela oferece tempo médio de acesso O(1) para inserção, busca e remoção. Ideal para tarefas onde é necessário encontrar dados rapidamente por uma chave.
use std::collections::HashMap;
fn main() { let mut scores = HashMap::new(); scores.insert(String::from("Alice"), 100); scores.insert(String::from("Bob"), 85); // Obtendo valor pela chave let alice_score = scores.get(&String::from("Alice")); match alice_score { Some(score) => println!("Alice marcou: {}", score), None => println!("Alice não encontrada"), } // Percorrendo todos os pares for (name, score) in &scores { println!("{}: {}", name, score); }}Strings (String)
Em Rust, existem dois tipos principais de strings: &str (fatia de string) e String (string mutável e proprietária). Trabalhar com strings em Rust requer atenção devido à codificação UTF-8.
fn main() { let mut hello = String::from("Olá, "); hello.push_str("mundo!"); // Adicionando string hello.push('!'); // Adicionando caractere println!("{}", hello); // Olá, mundo!! // Fatias de string (cuidado com limites de caracteres!) let slice = &hello[0..5]; // "Olá," println!("Fatia: {}", slice);}2. Implementação de Pilha e Fila
Pilha e fila são estruturas de dados fundamentais frequentemente usadas em algoritmos. Em Rust, podem ser facilmente implementadas usando Vec.
Pilha (LIFO — Last In, First Out)
A pilha funciona pelo princípio "o último a entrar é o primeiro a sair". Vec já fornece os métodos push e pop, que implementam o comportamento de pilha.
fn main() { let mut stack: Vec<i32> = Vec::new(); stack.push(1); stack.push(2); stack.push(3); while let Some(top) = stack.pop() { println!("Removendo: {}", top); } // Saída: 3, 2, 1}