Structs em Rust
Uma struct (estrutura) agrupa valores relacionados em um único tipo com nome. Enquanto uma tupla é um pacote rápido e sem nomes, a struct dá um nome a cada campo, e isso deixa o código que a usa muito mais fácil de ler.
Definindo e criando uma struct
Seção intitulada “Definindo e criando uma struct”struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30, };
println!("{} tem {} anos", alice.name, alice.age);}Saída:
Alice tem 30 anosCompare com uma tupla ("Alice", 30): com uma struct, alice.name e alice.age se explicam sozinhos. Você não precisa lembrar que o campo 0 é o nome e o campo 1 é a idade.
Tornando uma struct mutável
Seção intitulada “Tornando uma struct mutável”Assim como as variáveis, as instâncias de uma struct são imutáveis por padrão. Adicione mut para poder mudar um campo depois de criá-la:
struct Person { name: String, age: u32,}
fn main() { let mut alice = Person { name: String::from("Alice"), age: 30, };
alice.age += 1; println!("{} agora tem {} anos", alice.name, alice.age);}Adicionando comportamento com métodos
Seção intitulada “Adicionando comportamento com métodos”Você pode ligar funções a uma struct usando um bloco impl. Lá dentro, &self se refere à instância específica em que o método foi chamado:
struct Person { name: String, age: u32,}
impl Person { fn greet(&self) { println!("Oi, eu sou {} e tenho {} anos", self.name, self.age); }}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; alice.greet();}Saída:
Oi, eu sou Alice e tenho 30 anosFunções construtoras
Seção intitulada “Funções construtoras”Um padrão comum é criar uma função new dentro do impl que monta a instância para você. Assim, quem usa a struct não precisa escrever todos os campos à mão:
struct Person { name: String, age: u32,}
impl Person { fn new(name: &str, age: u32) -> Person { Person { name: name.to_string(), age } }}
fn main() { let bob = Person::new("Bob", 25); println!("{} tem {} anos", bob.name, bob.age);}Repare no age sozinho dentro de Person { name: name.to_string(), age }. Quando uma variável tem exatamente o mesmo nome do campo, você pode pular o age: age e escrever só age. O Rust chama isso de field init shorthand (atalho de inicialização de campo).
Imprimindo uma struct com derive
Seção intitulada “Imprimindo uma struct com derive”Tente usar println!("{:?}", alice) com a struct como está escrita acima e o código não compila: o Rust não sabe imprimir um tipo seu para depuração, a menos que você peça. Coloque #[derive(Debug)] acima da struct e ganhe isso de graça:
#[derive(Debug)]struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Alice"), age: 30 }; println!("{:?}", alice);}Saída:
Person { name: "Alice", age: 30 }#[derive(...)] é o jeito do Rust de gerar automaticamente funcionalidades comuns para um tipo. Você vai ver Debug, e mais tarde Clone e outros, sendo adicionados assim o tempo todo.
Prática
Seção intitulada “Prática”1. Métodos de retângulo
Seção intitulada “1. Métodos de retângulo”Crie uma struct Rectangle com os campos width (largura) e height (altura), do tipo u32. Dê a ela dois métodos: area, que devolve a área, e is_square, que devolve true quando os dois lados são iguais. Dica: os dois métodos só leem os campos, então recebem &self.
Ver solução
struct Rectangle { width: u32, height: u32,}
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn is_square(&self) -> bool { self.width == self.height }}
fn main() { let wide = Rectangle { width: 8, height: 3 }; let square = Rectangle { width: 5, height: 5 };
println!("largo: área {}, quadrado? {}", wide.area(), wide.is_square()); println!("quadrado: área {}, quadrado? {}", square.area(), square.is_square());}Saída:
largo: área 24, quadrado? falsequadrado: área 25, quadrado? true2. Uma conta bancária
Seção intitulada “2. Uma conta bancária”Crie uma struct Account com um owner (dono, String) e um balance (saldo) em centavos (u32). Escreva Account::new("Ana"), que começa com saldo 0. Depois, deposite 50 e 25 centavos mudando o campo, e imprima a conta de dois jeitos: com uma frase normal e com {:?}. Dica: a variável precisa ser let mut para você poder mudar account.balance, e {:?} precisa de #[derive(Debug)].
Ver solução
#[derive(Debug)]struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }}
fn main() { let mut account = Account::new("Ana"); account.balance += 50; account.balance += 25;
println!("{} tem {} centavos", account.owner, account.balance); println!("{:?}", account);}Saída:
Ana tem 75 centavosAccount { owner: "Ana", balance: 75 }O próprio new define balance: 0, então quem chama não tem como esquecer. Esse é um dos grandes motivos de as funções construtoras serem tão comuns.
3. Uma lista de pessoas
Seção intitulada “3. Uma lista de pessoas”Coloque três valores Person em um vetor: Ana (34), Ben (16) e Chen (52). Imprima o nome de todo mundo que tem 18 anos ou mais e, depois, quem é a pessoa mais velha. Dica: percorra com for person in &people e guarde a pessoa mais velha até agora com let mut oldest = &people[0];.
Ver solução
struct Person { name: String, age: u32,}
fn main() { let people = vec![ Person { name: String::from("Ana"), age: 34 }, Person { name: String::from("Ben"), age: 16 }, Person { name: String::from("Chen"), age: 52 }, ];
let mut oldest = &people[0]; for person in &people { if person.age >= 18 { println!("{} é maior de idade", person.name); } if person.age > oldest.age { oldest = person; } }
println!("Mais velha: {} ({})", oldest.name, oldest.age);}Saída:
Ana é maior de idadeChen é maior de idadeMais velha: Chen (52)oldest é uma referência (&Person) para dentro do vetor, não uma cópia, então nada é clonado. Cada vez que encontramos alguém mais velho, só fazemos oldest apontar para essa pessoa.
- Uma struct agrupa campos com nome em um único tipo:
struct Person { name: String, age: u32 }. - Crie uma instância com
Person { name: ..., age: ... }; marque commutpara mudar os campos depois. - Adicione comportamento com um bloco
impl;&selfse refere à instância em que o método foi chamado. - Adicione
#[derive(Debug)]para poder imprimir a struct com{:?}.
Teste o que aprendeu
Seção intitulada “Teste o que aprendeu”Teste o que aprendeu
1. Qual é a principal vantagem de uma struct sobre uma tupla?
2. O que adicionar #[derive(Debug)] a uma struct permite fazer?
3. Em Person { name: name.to_string(), age }, o que faz o age sozinho?
Pontuação: 0 / 3
Para saber mais
Seção intitulada “Para saber mais”- Defining and Instantiating Structs — The Rust Book (em inglês): também explica a sintaxe de atualização de struct (
..other), as tuple structs e as structs sem campos (unit-like). - An Example Program Using Structs — The Rust Book (em inglês): refatora um programa de retângulos, de variáveis soltas para uma tupla e depois para uma struct, para você ver por que as structs ajudam.
- Method Syntax — The Rust Book (em inglês):
&selfvs&mut self, métodos com parâmetros extras e funções associadas comonew. - Structures — Rust by Example (em inglês): exemplos executáveis de todos os tipos de struct, com um pequeno exercício no fim.
- Derive — Rust by Example (em inglês): o que o
#[derive(...)]pode gerar para você, comoDebug,CloneePartialEq.