Tipos compostos: tuplas e arrays
Esta aula aborda os tipos compostos em Rust, focando em tuplas e arrays. Você aprenderá a declarar, acessar e desestruturar tuplas, além de trabalhar com arrays de tamanho fixo e seus limites de acesso.
Em Rust, além dos tipos escalares (inteiros, ponto flutuante, booleanos e caracteres), existem tipos compostos que permitem agrupar múltiplos valores em uma única estrutura. Nesta aula, exploraremos dois tipos compostos fundamentais: tuplas e arrays. As tuplas podem conter valores de tipos diferentes, enquanto os arrays armazenam múltiplos valores do mesmo tipo com tamanho fixo em tempo de compilação. Compreender esses tipos é essencial para organizar dados de forma eficiente e segura.
Tuplas e desestruturação
Uma tupla é uma coleção de valores de tipos potencialmente diferentes, agrupados entre parênteses e separados por vírgulas. O tipo da tupla é definido pelos tipos de cada elemento na ordem em que aparecem. Por exemplo, (i32, f64, char) é uma tupla que contém um inteiro, um ponto flutuante e um caractere. As tuplas têm tamanho fixo: uma vez declaradas, não podem crescer ou encolher.
Para acessar os elementos de uma tupla, podemos usar a indexação por ponto (tupla.0, tupla.1, etc.) ou a desestruturação (pattern matching). A desestruturação permite quebrar a tupla em variáveis individuais de forma concisa. Rust também oferece a possibilidade de ignorar alguns elementos com o padrão _.
fn main() {
// Declaração de uma tupla
let pessoa: (&str, u8, char) = ("Alice", 30, 'F');
// Acesso por índice
println!("Nome: {}", pessoa.0);
println!("Idade: {}", pessoa.1);
println!("Gênero: {}", pessoa.2);
// Desestruturação
let (nome, idade, genero) = pessoa;
println!("{} tem {} anos e é do gênero {}.", nome, idade, genero);
// Ignorando elementos com _
let (_, idade, _) = pessoa;
println!("Idade extraída: {}", idade);
}
A desestruturação é muito útil em funções que retornam tuplas, permitindo extrair múltiplos valores de uma só vez. Além disso, tuplas podem ser aninhadas, criando estruturas mais complexas.
Arrays de tamanho fixo
Arrays em Rust são coleções de elementos do mesmo tipo, com tamanho conhecido em tempo de compilação. A sintaxe é [tipo; tamanho], e os valores são inicializados entre colchetes. Por exemplo, [i32; 5] é um array de 5 inteiros. Arrays são armazenados na pilha (stack) e têm desempenho previsível.
Para criar um array, você pode listar todos os elementos ou usar a sintaxe de inicialização com valor padrão: [valor_inicial; tamanho]. O acesso aos elementos é feito através de índices começando em 0, usando colchetes: array[indice].
fn main() {
// Array com valores explícitos
let numeros: [i32; 5] = [10, 20, 30, 40, 50];
// Array com valor padrão (todos os elementos são 0)
let zeros: [i32; 3] = [0; 3];
println!("Primeiro número: {}", numeros[0]);
println!("Segundo número: {}", numeros[1]);
// Iterando sobre um array
for num in &numeros {
println!("Elemento: {}", num);
}
}
É importante notar que arrays têm tamanho fixo. Se você precisa de uma coleção que pode crescer, use um Vec (vetor), que será abordado em aulas futuras.
Acesso e limites
O acesso a elementos de array em Rust é verificado em tempo de execução. Se você tentar acessar um índice fora do intervalo válido (0 até tamanho-1), o programa entrará em pânico (panic) e abortará. Isso é uma característica de segurança que evita acessos inválidos à memória.
Para evitar pânicos, você pode usar métodos como get(), que retorna um Option (Some ou None), permitindo tratar o caso de índice inválido sem crash. Além disso, é comum usar loops com intervalos seguros ou iteração direta sobre o array.
fn main() {
let arr = [1, 2, 3, 4, 5];
// Acesso seguro com get()
match arr.get(10) {
Some(valor) => println!("Valor: {}", valor),
None => println!("Índice fora dos limites"),
}
// Acesso direto (pode causar panic)
// println!("{}", arr[10]); // Isso vai causar panic!
// Iteração segura com for
for i in 0..arr.len() {
println!("arr[{}] = {}", i, arr[i]);
}
}
Para arrays, o método .len() retorna o número de elementos. Sempre verifique os limites antes de acessar diretamente, ou prefira usar get() quando o índice puder ser inválido.
Boas práticas
Ao trabalhar com tuplas, prefira desestruturar quando precisar de múltiplos valores, pois torna o código mais legível. Use tuplas para agrupar dados relacionados que não justificam a criação de uma struct. Para arrays, lembre-se de que o tamanho é parte do tipo; portanto, [i32; 3] e [i32; 4] são tipos diferentes. Se precisar de um array de tamanho dinâmico, considere usar slices ou vetores.
Referências
- The Rust Programming Language - Tuples
- The Rust Programming Language - Arrays
- Rust std::primitive::tuple
- Rust std::primitive::array
- Rust by Example - Tuples
- Rust by Example - Arrays
Exercícios
-
Crie uma tupla chamada
pontocom dois valoresf64representando coordenadas (x, y). Em seguida, desestruture-a e imprima cada coordenada.✓ Resposta:fn main() { let ponto: (f64, f64) = (3.5, 7.2); let (x, y) = ponto; println!("x = {}, y = {}", x, y); } -
Declare um array de 4 inteiros com os valores 1, 2, 3 e 4. Use um loop
forpara imprimir cada elemento multiplicado por 2.✓ Resposta:fn main() { let arr = [1, 2, 3, 4]; for &valor in arr.iter() { println!("{}", valor * 2); } } -
Escreva uma função que recebe uma tupla
(i32, i32)e retorna a soma dos dois elementos.✓ Resposta:fn soma_tupla(t: (i32, i32)) -> i32 { t.0 + t.1 } fn main() { let resultado = soma_tupla((5, 10)); println!("Soma: {}", resultado); } -
Crie um array de 5 inteiros inicializados com o valor 7 (usando a sintaxe de valor padrão). Depois, tente acessar o índice 5 usando
get()e trate o resultado.✓ Resposta:fn main() { let arr = [7; 5]; match arr.get(5) { Some(valor) => println!("Valor: {}", valor), None => println!("Índice 5 está fora dos limites (tamanho {})", arr.len()), } } -
Escreva um programa que itere sobre um array de strings (
&str) e imprima cada string em maiúsculas. Use o métodoto_uppercase().✓ Resposta:fn main() { let palavras = ["hello", "world", "rust"]; for &palavra in palavras.iter() { println!("{}", palavra.to_uppercase()); } }