Pular para o conteúdo

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.

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 anos
▶ Experimente você mesmo

Compare 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.

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);
}
▶ Experimente você mesmo

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 anos
▶ Experimente você mesmo

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);
}
▶ Experimente você mesmo

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).

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 }
▶ Experimente você mesmo

#[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.

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? false
quadrado: área 25, quadrado? true
▶ Experimente você mesmo

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 centavos
Account { owner: "Ana", balance: 75 }
▶ Experimente você mesmo

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.

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 idade
Chen é maior de idade
Mais velha: Chen (52)
▶ Experimente você mesmo

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 com mut para mudar os campos depois.
  • Adicione comportamento com um bloco impl; &self se refere à instância em que o método foi chamado.
  • Adicione #[derive(Debug)] para poder imprimir a struct com {:?}.

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