Structs: definindo e instanciando
1. Introdução às Structs
Structs são um dos tipos de dados compostos mais importantes em Rust. Elas permitem agrupar valores relacionados sob um único nome, criando tipos personalizados que representam conceitos do mundo real. Diferentemente das tuplas, que acessam elementos por posição, structs nomeiam cada campo, tornando o código mais legível e auto-documentado.
Rust oferece três variações de structs:
- Named-field structs (structs tradicionais com campos nomeados)
- Tuple structs (structs que se comportam como tuplas)
- Unit-like structs (structs sem campos)
Cada tipo serve a propósitos específicos, como veremos ao longo deste artigo.
2. Definindo uma Struct Tradicional (named-field struct)
A sintaxe para definir uma struct tradicional utiliza a palavra-chave struct seguida do nome e um bloco de campos com nome e tipo:
struct Usuario {
nome: String,
idade: u8,
ativo: bool,
}
Aqui, Usuario é um tipo composto que armazena três informações: um nome (String), uma idade (u8) e um status booleano. A convenção em Rust é usar PascalCase para nomes de structs e snake_case para campos.
3. Instanciando Structs
Para criar uma instância de uma struct, especificamos valores para todos os campos:
let usuario1 = Usuario {
nome: String::from("Alice"),
idade: 30,
ativo: true,
};
Acessamos campos individuais com a notação de ponto:
println!("Nome: {}", usuario1.nome);
println!("Idade: {}", usuario1.idade);
Por padrão, instâncias são imutáveis. Para modificar campos, declaramos a variável como mutável:
let mut usuario2 = Usuario {
nome: String::from("Bob"),
idade: 25,
ativo: false,
};
usuario2.ativo = true; // Permitido porque usuario2 é mutável
Rust oferece um atalho elegante chamado field init shorthand quando nomes de variáveis coincidem com nomes de campos:
fn criar_usuario(nome: String, idade: u8) -> Usuario {
Usuario {
nome, // equivale a nome: nome
idade, // equivale a idade: idade
ativo: true,
}
}
4. Construtores e Funções Associadas
Funções associadas são implementadas dentro de um bloco impl e não recebem self como parâmetro. O padrão mais comum é criar um construtor new():
impl Usuario {
fn new(nome: String, idade: u8) -> Self {
Self {
nome,
idade,
ativo: true, // valor default
}
}
fn novo_admin(nome: String) -> Self {
Self {
nome,
idade: 0, // valor default para admin
ativo: true,
}
}
}
// Uso:
let usuario3 = Usuario::new(String::from("Carlos"), 28);
let admin = Usuario::novo_admin(String::from("Diana"));
Note que Self é um alias para o tipo da struct (no caso, Usuario). Construtores permitem definir valores padrão e validar parâmetros antes da criação.
5. Atualização de Structs com Struct Update Syntax
Frequentemente precisamos criar uma nova instância baseada em outra, alterando apenas alguns campos. A struct update syntax com .. simplifica isso:
let usuario4 = Usuario {
nome: String::from("Eva"),
..usuario1 // copia os campos restantes de usuario1
};
Isso equivale a:
let usuario4 = Usuario {
nome: String::from("Eva"),
idade: usuario1.idade,
ativo: usuario1.ativo,
};
Cuidado com ownership: se algum campo não implementar Copy (como String), a instância original não poderá mais ser usada após a cópia parcial:
let usuario5 = Usuario {
nome: String::from("Frank"),
..usuario1 // usuario1.nome é movido para usuario5
};
// println!("{}", usuario1.nome); // Erro! nome foi movido
6. Tuple Structs (structs tipo tupla)
Tuple structs combinam a nomeação de structs com o acesso posicional de tuplas:
struct Cor(u8, u8, u8);
struct Ponto(f64, f64);
let vermelho = Cor(255, 0, 0);
let origem = Ponto(0.0, 0.0);
// Acesso posicional:
println!("R: {}, G: {}, B: {}", vermelho.0, vermelho.1, vermelho.2);
O newtype pattern é um caso de uso comum, onde envolvemos um tipo primitivo para ganhar segurança de tipo:
struct Polegadas(f64);
struct Centimetros(f64);
fn calcular_area(comprimento: Polegadas, largura: Polegadas) -> f64 {
comprimento.0 * largura.0
}
// Isso evita passar Centimetros onde se espera Polegadas
Diferentemente de tuplas comuns, tuple structs criam tipos distintos, mesmo que tenham os mesmos tipos internos.
7. Unit-Like Structs (structs sem campos)
Unit-like structs não possuem campos:
struct Marker;
struct NaoImprimivel;
São úteis para:
- Marcadores de tipo: indicar que um tipo implementa um trait
- Traits markers: como Send e Sync em tipos personalizados
Instanciação é simples:
let _m = Marker;
let _n = NaoImprimivel;
Exemplo prático com trait:
trait Identificavel {}
struct UsuarioValido;
struct UsuarioPendente;
impl Identificavel for UsuarioValido {}
impl Identificavel for UsuarioPendente {}
fn processar(item: impl Identificavel) {
println!("Processando...");
}
processar(UsuarioValido);
processar(UsuarioPendente);
8. Ownership e Borrowing em Structs
Structs podem conter dados owned (como String, Vec) ou referências. Quando usamos referências, precisamos de lifetimes:
struct UsuarioRef<'a> {
nome: &'a str, // referência a uma string
idade: u8,
}
let nome = String::from("Alice");
let usuario = UsuarioRef {
nome: &nome,
idade: 30,
}; // Válido enquanto 'nome' existir
Movendo ownership: quando passamos uma struct para uma função, a propriedade é transferida:
fn consumir(u: Usuario) {
println!("{}", u.nome);
} // u é dropado aqui
let u = Usuario::new(String::from("Teste"), 20);
consumir(u);
// println!("{}", u.nome); // Erro! u foi movido
Para evitar movimentos, implementamos Copy e Clone:
#[derive(Debug, Clone, Copy)]
struct Coordenada {
x: i32,
y: i32,
}
let a = Coordenada { x: 10, y: 20 };
let b = a; // Copy, não move
println!("{:?}, {:?}", a, b); // Ambos ainda válidos
Structs que contêm apenas tipos Copy (como inteiros, bools, floats) podem derivar Copy. Para tipos com String ou Vec, apenas Clone é possível.
Structs são fundamentais para organizar dados em Rust. Dominar sua definição, instanciação e os diferentes padrões (named-field, tuple, unit-like) permite criar abstrações seguras e expressivas. A combinação com ownership e traits torna structs uma ferramenta poderosa para modelar domínios complexos com segurança de memória garantida pelo compilador.
Referências
- The Rust Programming Language - Chapter 5: Using Structs to Structure Related Data — Capítulo oficial do livro sobre structs, com exemplos detalhados de definição, instanciação e métodos
- Rust by Example: Structs — Tutoriais práticos com exemplos interativos de todos os tipos de structs
- Rust Reference: Struct Types — Documentação técnica de referência sobre a sintaxe e semântica de structs
- Rust Design Patterns: Newtype Pattern — Guia sobre o padrão newtype usando tuple structs para segurança de tipo
- Rust RFC 42: Struct Update Syntax — Proposta original e discussão técnica sobre a sintaxe de atualização de structs com
..