Tipos compostos: tuplas e arrays
Nesta aula, exploramos tipos compostos em Rust: tuplas e arrays. Aprendemos a criar e desestruturar tuplas, trabalhar com arrays de tamanho fixo e realizar acesso seguro a elementos, respeitando os limites de índice.
Em Rust, além dos tipos escalares, temos tipos compostos que agrupam múltiplos valores. Dois dos mais fundamentais são tuplas e arrays. Eles permitem armazenar coleções de dados, mas com diferenças importantes: tuplas podem conter tipos diferentes e têm tamanho fixo, enquanto arrays têm todos os elementos do mesmo tipo e tamanho fixo.
Dominar esses tipos é essencial para organizar dados de forma eficiente e segura, aproveitando o sistema de tipos do Rust para evitar erros comuns, como acessar índices fora dos limites.
Tuplas e desestruturação
Uma tupla é uma coleção de valores de tipos potencialmente diferentes, agrupados entre parênteses. O tamanho da tupla é fixo e não pode ser alterado após a declaração. Tuplas são úteis para retornar múltiplos valores de uma função ou para agrupar dados relacionados de forma leve.
Exemplo de criação de tupla:
let tup: (i32, f64, u8) = (500, 6.4, 1);Para acessar os elementos individuais de uma tupla, podemos usar a desestruturação, que quebra a tupla em variáveis separadas, ou a notação de ponto com o índice:
let tup = (500, 6.4, 1);
let (x, y, z) = tup; // desestruturação
println!("Valor de y: {y}"); // 6.4
let primeiro = tup.0; // acesso por índice
println!("Primeiro elemento: {primeiro}");A desestruturação é uma forma concisa e expressiva de extrair valores. Também é possível ignorar alguns valores usando _:
let tup = (1, 2, 3);
let (_, segundo, _) = tup;
println!("Segundo: {segundo}");Tuplas também podem ser aninhadas e usadas em expressões match. Um padrão comum é retornar tuplas de funções:
fn calcula(x: i32) -> (i32, i32) {
(x * 2, x * 3)
}
let (dobro, triplo) = calcula(5);
println!("Dobro: {dobro}, Triplo: {triplo}");Arrays de tamanho fixo
Um array em Rust é uma coleção de elementos do mesmo tipo, com tamanho fixo conhecido em tempo de compilação. Eles são alocados na pilha (stack) e são eficientes para armazenar dados que não precisam crescer. A sintaxe é [tipo; tamanho].
Exemplo de declaração e inicialização:
let a: [i32; 5] = [1, 2, 3, 4, 5]; // array com 5 inteiros
let b = [3; 5]; // [3, 3, 3, 3, 3] (5 elementos iguais a 3)Arrays são úteis quando você sabe exatamente quantos elementos precisa, como dias da semana ou coordenadas fixas. Eles são diferentes de vetores (Vec), que são dinâmicos e alocados no heap.
Para acessar elementos, usamos índices entre colchetes:
let primeiro = a[0];
let ultimo = a[4];
println!("Primeiro: {primeiro}, Último: {ultimo}");O compilador verifica se o índice está dentro dos limites? Não, a verificação é feita em tempo de execução. Se você acessar um índice inválido, o programa entra em pânico (panic). Veremos isso na próxima seção.
Acesso e limites
Em Rust, o acesso a elementos de array (e tupla) é seguro quanto a limites? Para arrays, sim, em tempo de execução. Se você tentar acessar um índice fora do intervalo, o Rust causa um panic (pânico) e o programa aborta, evitando comportamento indefinido.
Exemplo de acesso inválido:
let a = [1, 2, 3];
let index = 10;
let element = a[index]; // isso vai causar panic em tempo de execução
println!("Elemento: {element}");Para evitar panics, você deve garantir que os índices estejam dentro dos limites. Uma prática comum é usar métodos como get() que retornam um Option:
let a = [1, 2, 3];
let index = 10;
match a.get(index) {
Some(valor) => println!("Valor: {valor}"),
None => println!("Índice fora dos limites"),
}Para tuplas, o acesso por índice (tup.0) é verificado em tempo de compilação: se você usar um índice que não existe, o compilador rejeita. Já para arrays, o índice é uma expressão, então a verificação é em tempo de execução.
Podemos iterar sobre arrays com loops seguros:
let a = [10, 20, 30];
for element in a {
println!("Valor: {element}");
}Ou com índices:
for i in 0..a.len() {
println!("a[{i}] = {}", a[i]);
}O método len() retorna o tamanho do array, que é conhecido em tempo de compilação.
Boas práticas
- Use tuplas para agrupar poucos valores de tipos diferentes quando não quiser criar uma struct.
- Prefira arrays quando o tamanho for fixo e conhecido; para coleções dinâmicas, use
Vec. - Sempre verifique índices dinâmicos antes de acessar arrays; use
get()para evitar panics. - Aproveite a desestruturação para tornar o código mais legível.
Referências
- The Rust Book: Data Types - Compound Types
- Rust Documentation: Tuple
- Rust Documentation: Array
- Rust by Example: Arrays
- Rust by Example: Tuples
- Rust Documentation: Option
Exercícios
Crie uma tupla chamada
pontocontendo dois valores: x (i32) e y (i32). Em seguida, desestruture-a em duas variáveis e imprima-as.✓ Resposta:let ponto: (i32, i32) = (10, 20); let (x, y) = ponto; println!("x: {x}, y: {y}");Declare um array de 4 inteiros: [1, 2, 3, 4]. Acesse o terceiro elemento (índice 2) e imprima-o. Depois, tente acessar um índice inválido (por exemplo, 5) e veja o que acontece. (Não precisa mostrar o panic, apenas descreva.)
✓ Resposta:let arr = [1, 2, 3, 4]; println!("Terceiro elemento: {}", arr[2]); // arr[5] causaria panic em tempo de execução: index out of boundsEscreva uma função que recebe uma tupla (i32, f64) e retorna uma nova tupla com os valores trocados (f64, i32).
✓ Resposta:fn troca(t: (i32, f64)) -> (f64, i32) { (t.1, t.0) } fn main() { let original = (5, 3.14); let trocada = troca(original); println!("Trocada: {:?}, {:?}", trocada.0, trocada.1); }Crie um array de 5 elementos com o valor inicial 0 usando a sintaxe de inicialização rápida. Em seguida, preencha cada posição com o quadrado do índice (i*i) usando um loop.
✓ Resposta:let mut arr = [0; 5]; for i in 0..arr.len() { arr[i] = (i * i) as i32; } println!("{:?}", arr); // [0, 1, 4, 9, 16]Use o método
get()para acessar o índice 2 de um array [10, 20, 30] e o índice 5. Imprima o valor ou uma mensagem de erro conforme o caso.✓ Resposta:let arr = [10, 20, 30]; for &idx in &[2, 5] { match arr.get(idx) { Some(val) => println!("arr[{}] = {}", idx, val), None => println!("Índice {} está fora dos limites", idx), } }