Tuplas em Rust
Uma tupla junta vários valores, que podem ser de tipos diferentes, em um só. É um jeito rápido de agrupar coisas relacionadas sem precisar criar um tipo novo só para elas.
Criando uma tupla
Seção intitulada “Criando uma tupla”fn main() { let person: (&str, i32, bool) = ("Alice", 30, true); println!("{:?}", person);}Saída:
("Alice", 30, true)Repare que a tupla mistura um &str, um i32 e um bool em um único valor. Um array não consegue fazer isso, porque todos os elementos de um array precisam ser do mesmo tipo.
Para imprimir uma tupla inteira, use {:?} (formatação de debug) em vez de {}. O {} simples não funciona com tuplas.
Acessando os elementos pela posição
Seção intitulada “Acessando os elementos pela posição”Use .0, .1, .2 e assim por diante. A contagem das posições começa em zero:
fn main() { let person = ("Alice", 30, true);
println!("Nome: {}", person.0); println!("Idade: {}", person.1); println!("Ativa: {}", person.2);}Saída:
Nome: AliceIdade: 30Ativa: trueA posição precisa ser um número escrito diretamente no código. Você não pode usar uma variável, como person.i, porque cada posição pode guardar um tipo diferente, e o Rust precisa saber o tipo na hora de compilar.
Desestruturando uma tupla
Seção intitulada “Desestruturando uma tupla”Dá para tirar cada valor e colocar em uma variável própria, tudo em uma linha:
fn main() { let person = ("Alice", 30, true); let (name, age, is_active) = person;
println!("{name} tem {age} anos, ativa: {is_active}");}Saída:
Alice tem 30 anos, ativa: trueIsso costuma ficar mais claro do que repetir .0, .1, .2, principalmente quando a tupla tem mais de dois ou três campos.
Retornando vários valores de uma função
Seção intitulada “Retornando vários valores de uma função”Uma função só pode declarar um tipo de retorno. Por isso, a tupla é o jeito natural de devolver mais de um valor:
fn min_max(numbers: &[i32]) -> (i32, i32) { let min = *numbers.iter().min().unwrap(); let max = *numbers.iter().max().unwrap(); (min, max)}
fn main() { let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]); println!("mínimo: {smallest}, máximo: {largest}");}Saída:
mínimo: 1, máximo: 9Não se preocupe ainda com &[i32], .iter() ou .unwrap(). Eles são explicados nas páginas de Slices, Iteradores e Option. O que importa aqui é o tipo de retorno (i32, i32) e como o main o desestrutura em duas variáveis logo de cara.
Alterando tuplas e trocando valores
Seção intitulada “Alterando tuplas e trocando valores”Se uma tupla for declarada com mut, você pode alterar os campos um de cada vez, usando as mesmas posições .0, .1. Os valores podem mudar, mas o formato não: uma tupla sempre mantém a quantidade e os tipos de campos com que começou.
A desestruturação também funciona no sentido contrário: você pode atribuir a variáveis que já existem. Isso dá uma troca de valores elegante em uma linha, sem variável temporária. Se você só quer alguns dos campos, escreva _ nos que não precisa:
fn main() { let mut point = (3, 4); point.0 += 10; // altera um campo no lugar println!("o ponto é {:?}", point);
let mut a = 1; let mut b = 2; (a, b) = (b, a); // troca os dois valores println!("a = {a}, b = {b}");
let (x, _) = point; // fica com x, ignora o segundo campo println!("x = {x}");}Saída:
o ponto é (13, 4)a = 2, b = 1x = 13A troca funciona porque o Rust monta primeiro o lado direito, (b, a). Só depois ele desempacota esse valor em a e b. Dá para fazer muito mais com _ e padrões na página de Pattern matching.
A tupla vazia: ()
Seção intitulada “A tupla vazia: ()”Na verdade, você já encontrou uma tupla sem perceber. () se chama tipo unit (unit type). É o que uma função retorna quando não tem nada de útil para devolver. É o tipo de retorno implícito de fn main().
Quando usar uma tupla e quando usar uma struct
Seção intitulada “Quando usar uma tupla e quando usar uma struct”Tuplas são ótimas para agrupamentos rápidos e sem nome, principalmente para retornar dois ou três valores de uma função. Quando um grupo de valores representa um conceito de verdade, com campos que você vai chamar pelo nome ao longo do código (como uma Person com nome e idade), uma struct costuma ser a escolha melhor e mais legível.
Prática
Seção intitulada “Prática”1. Soma e produto
Seção intitulada “1. Soma e produto”Escreva uma função sum_and_product(a: i32, b: i32) -> (i32, i32) que devolve a soma e o produto de dois números. No main, chame-a com 5 e 6 e desestruture o resultado. Dica: a última linha da função é a própria tupla, (a + b, a * b), sem ponto e vírgula.
Ver solução
fn sum_and_product(a: i32, b: i32) -> (i32, i32) { (a + b, a * b)}
fn main() { let (sum, product) = sum_and_product(5, 6); println!("soma = {sum}, produto = {product}");}Saída:
soma = 11, produto = 302. Um retângulo feito de dois cantos
Seção intitulada “2. Um retângulo feito de dois cantos”Um retângulo pode ser guardado como uma tupla de dois pontos, em que cada ponto também é uma tupla: let rect = ((1, 2), (5, 5));. Calcule a largura, a altura e a área. Dica: tuplas podem ser aninhadas, e rect.1.0 quer dizer “o primeiro campo do segundo ponto”.
Ver solução
fn main() { let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0; let height = rect.1.1 - rect.0.1;
println!("largura {width}, altura {height}, área {}", width * height);}Saída:
largura 4, altura 3, área 12Você também pode desestruturar os dois pontos de uma vez: let ((x1, y1), (x2, y2)) = rect;. Dá mais trabalho para digitar, mas x2 - x1 é mais fácil de ler do que rect.1.0 - rect.0.0. Quando os pontos começam a se acumular assim, é sinal de que uma struct com campos nomeados ficaria mais clara.
- Uma tupla agrupa valores que podem ser de tipos diferentes:
(name, age, is_active). - Acesse os campos com
.0,.1,.2ou tire todos de uma vez com desestruturação. - Tuplas são um jeito natural de retornar vários valores de uma função.
- Os campos de uma tupla
mutpodem mudar, mas o tamanho e os tipos não.(a, b) = (b, a);troca dois valores. - Para agrupamentos com nome e que você vai reutilizar, prefira uma struct.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. O que uma tupla consegue guardar e um array não?
2. Como você acessa o segundo elemento de uma tupla chamada person?
3. Depois de let mut a = 1; let mut b = 2; (a, b) = (b, a);, quanto valem a e b?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- The Tuple Type — The Rust Book (em inglês): a introdução oficial a como criar, desestruturar e acessar tuplas pela posição.
- Tuples — Rust by Example (em inglês): exemplos executáveis com tuplas aninhadas e tuplas como argumentos de funções.
- O tipo
tuple— documentação da std (em inglês): quais traits as tuplas implementam, como comparar, copiar e imprimir com{:?}. - O tipo
unit— documentação da std (em inglês): o que é o()e onde ele aparece. - Destructuring assignments — The Rust Reference (em inglês): as regras exatas por trás de
(a, b) = (b, a);.