Rust: Estruturas em Rust (Struct)
Última atualização: 2026-08-26
As estruturas (Struct) são a principal ferramenta para definir tipos de dados personalizados em Rust — elas agrupam dados relacionados em um todo significativo.
Se uma variável é “um único dado”, então uma estrutura é “um conjunto de dados relacionados”. É como uma tabela — cada linha é uma instância e cada coluna é um campo.
1. O que você vai aprender
- Use a palavra-chave
structpara definir uma estrutura nomeada - Criar uma instância de uma estrutura e acessar seus campos
- Adicione métodos e funções associadas usando o bloco
impl - Estrutura de tupla e estrutura de unidade
- Propriedade e referências dos campos da estrutura
- Sintaxe para atualizar as estruturas
..e#[derive(Debug)]
2. Diagramas conceituais
flowchart LR
subgraph "struct Definition"
DEF["struct User"]
F1["username: String"]
F2["email: String"]
F3["active: bool"]
F4["sign_in_count: u64"]
end
subgraph "Instantiation"
INS["let user = User { ... }"]
V1["username: 'alice'"]
V2["email: 'alice@example.com'"]
V3["active: true"]
V4["sign_in_count: 1"]
end
F1 -.->|"Field Mapping"| V1
F2 -.->|"Field Mapping"| V2
F3 -.->|"Field Mapping"| V3
F4 -.->|"Field Mapping"| V4
3. A história de um sistema de gestão de alunos
(1) O Desafio: Como lidar com alunos com variáveis dispersas
Maria é uma professora que precisa gerenciar informações sobre os alunos de sua turma. Cada aluno tem:
- Nome, idade, série, média geral
No início, ela escreveu:
let name1 = "Xiao Ming";
let age1 = 18;
let grade1 = "Senior Year";
let gpa1 = 3.8;
let name2 = "Xiao Hong";
let age2 = 17;
let grade2 = "11th Grade";
let gpa2 = 3.5;
Quando o número de alunos chega a 40, essas variáveis dispersas tornam-se completamente incontroláveis. Para imprimir um histórico escolar, é preciso escrever 40 linhas de código, e alterar um único campo significa fazer alterações em quatro lugares diferentes.
(2) A abordagem das estruturas no Rust
struct Student {
name: String,
age: u8,
grade: String,
gpa: f64,
}
fn main() {
let student1 = Student {
name: String::from("Xiao Ming"),
age: 18,
grade: String::from("Senior 3"),
gpa: 3.8,
};
let student2 = Student {
name: String::from("Xiao Hong"),
age: 17,
grade: String::from("Senior 2"),
gpa: 3.5,
};
println!("{}: {} years old, Grade: {}, GPA: {:.1}",
student1.name, student1.age, student1.grade, student1.gpa);
println!("{}: {} years old, Grade: {}, GPA: {:.1}",
student2.name, student2.age, student2.grade, student2.gpa);
}
Uma estrutura é como um “modelo” — ela define quais informações um “aluno” deve conter. Cada instância é como um formulário preenchido. O acesso aos campos é feito por meio de
., o que é claro e seguro.
4. Tipos de estruturas
graph TB
A[struct Type] --> B[Naming Structures: Fields have names and types]
A --> C[Tuple Structure: The field has a type but no name]
A --> D[Unit Structure: No fields]
B --> E[struct User { name: String, age: u8 }]
C --> F[struct Color(i32, i32, i32)]
D --> G[struct EmptyMarker]
| Tipo | Definição | Casos de uso |
|---|---|---|
| Convenções de nomenclatura | struct S { f1: T, f2: T } |
Na maioria dos casos, os nomes dos campos são autoexplicativos |
| Estrutura da tupla | struct S(T1, T2) |
Encapsula um único conceito (por exemplo, cor RGB, coordenadas) |
| Estrutura da unidade | struct S; |
Marcador de tipo, espaço reservado para a implementação da característica |
(2) Métodos versus funções de associação
| Tipo | Primeiro parâmetro | Convenção de chamada | Propriedade | Usos típicos |
|---|---|---|---|---|
| Método | &self |
obj.method() |
Obter | Ler dados da instância |
| Método | &mut self |
obj.method() |
Empréstimo de variáveis | Modificação de dados de instância |
| Método | self |
obj.method() |
Consumo | Converter/Destruir instância |
| Funções relacionadas | Nenhuma | Type::function() |
— | Construtores, métodos de fábrica |
(3) Referência rápida para características derivadas comumente utilizadas
| Característica | Funcionalidade | Condições automáticas | Exemplo |
|---|---|---|---|
Debug |
{:?} Impressão formatada |
Depurar todos os campos | #[derive(Debug)] |
Clone |
.clone() Cópia profunda |
Clonar todos os campos | #[derive(Clone)] |
Copy |
Cópia automática na atribuição | Clonar + Copiar todos os campos | #[derive(Copy, Clone)] |
PartialEq |
== / != Comparação |
Todos os campos implementam PartialEq | #[derive(PartialEq)] |
Hash |
Usado como chave do HashMap | Todos os campos implementam Hash | #[derive(Hash)] |
Default |
Valor padrão | Todos os campos têm um valor padrão | #[derive(Default)] |
5. Exemplos de estrutura
▶ Exemplo 1: Uso completo de estruturas nomeadas (Dificuldade ⭐)
// ============================================
// Naming Structures: Definition, Instantiation, Field Access
// ============================================
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
fn main() {
// Create an Instance
let rect = Rectangle {
width: 30,
height: 50,
};
// Access Fields
println!("Width: {}, Height: {}", rect.width, rect.height);
// Variable Instances
let mut mutable_rect = Rectangle {
width: 10,
height: 20,
};
mutable_rect.width = 15; // ✅ Can be modified
println!("Width after modification: {}", mutable_rect.width);
// Use Debug Print
println!("rect: {:?}", rect);
println!("rect (pretty): {:#?}", rect);
}
Resultado:
Width: 30, Height: 50
Width after modification: 15
rect: Rectangle { width: 30, height: 50 }
rect (pretty): Rectangle {
width: 30,
height: 50,
}
#[derive(Debug)]Permite que estruturas sejam exibidas usando a formatação{:?}ou{:#?}. Esse é um dos traços derivados mais comumente usados durante a depuração.
▶ Exemplo 2: Sintaxe para atualizar estruturas e notação abreviada de campos (Dificuldade ⭐⭐)
// ============================================
// Initializing Field Abbreviations + Update Syntax
// ============================================
#[derive(Debug)]
struct User {
username: String,
email: String,
active: bool,
sign_in_count: u64,
}
fn build_user(username: String, email: String) -> User {
User {
username, // When field names and variable names are the same, they can be written in shorthand.
email, // Equivalent to email: email
active: true,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("alice"),
String::from("alice@example.com"),
);
// Update Syntax: Based on user1 create user2, modify only email and username
let user2 = User {
email: String::from("alice_new@example.com"),
username: String::from("alice_new"),
..user1 // Other fields copied from user1 (Note: String fields will move!)
};
// println!("user1: {}", user1.username); // ❌ username was moved to user2
println!("user2: {:?}", user2);
println!("user1.active: {}", user1.active); // ✅ bool is Copy, still valid
}
Resultado:
user2: User { username: "alice_new", email: "alice_new@example.com", active: true, sign_in_count: 1 }
user1.active: true
Abreviação de campo: Quando o nome de uma variável coincide com o nome de um campo de uma estrutura, os dois pontos em
field: fieldpodem ser omitidos. A sintaxe atualizada..copia os campos restantes de outra instância — observe que isso utiliza a semântica de movimentação, em vez de Clone.
▶ Exemplo 3: O bloco impl — Adicionando métodos a uma estrutura (Dificuldade: ⭐⭐)
// ============================================
// impl block: Methods (&self) and associated functions (no &self)
// ============================================
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
// Methods: &self is a reference to a Rectangle instance
fn area(&self) -> u32 {
self.width * self.height
}
fn can_hold(&self, other: &Rectangle) -> bool {
self.width > other.width && self.height > other.height
}
// Associative Functions (similar to static methods): no &self
fn square(size: u32) -> Rectangle {
Rectangle {
width: size,
height: size,
}
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
let rect2 = Rectangle {
width: 10,
height: 20,
};
let square = Rectangle::square(15); // Correlation functions are used to :: Call
println!("rect1 Area: {}", rect1.area()); // 1500
println!("rect1 Can it accommodate? rect2: {}", rect1.can_hold(&rect2)); // true
println!("Area of a Square: {}", square.area()); // 225
}
Resultado:
rect1 Area: 1500
rect1 Can it accommodate? rect2: true
Area of a Square: 225
O primeiro parâmetro de um método (Method) é
&self(ou&mut self), e ele é chamado por meio de.. Uma função associada (Associated Function) não possui&selfe é chamada por meio de::— um exemplo clássico éString::from().
▶ Exemplo 4: Estruturas de tuplas (Dificuldade ⭐⭐)
// ============================================
// Tuple Structure: Suitable for "it's just a bunch of values" scenarios
// ============================================
#[derive(Debug)]
struct Color(i32, i32, i32); // RGB Color
#[derive(Debug)]
struct Point(i32, i32, i32); // 3D Coordinates
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
// Fields Accessed via Indexes (tuple-like)
println!("Black: R={}, G={}, B={}", black.0, black.1, black.2);
println!("The Beginning: x={}, y={}, z={}", origin.0, origin.1, origin.2);
// Tuple structures are not the same as tuples!
// let wrong: Color = origin; // ❌ Compilation Error: Point cannot be assigned to Color
// Although their internal structures are the same, they are different types
// This prevents "mixing up colors and coordinates" bugs
}
Resultado:
Black: R=0, G=0, B=0
The Beginning: x=0, y=0, z=0
Uma estrutura de tupla não possui nomes de campos, apenas tipos. É semelhante a uma tupla, mas possui seu próprio nome de tipo.
Color(0,0,0)ePoint(0,0,0)são idênticos internamente, mas têm tipos diferentes — isso evita confusão.
▶ Exemplo 5: Exercício abrangente — Sistema de gestão de alunos (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example: Structure-Based Approach, Associated Functions and Update Syntax
// ============================================
#[derive(Debug, Clone)]
struct Student {
name: String,
age: u8,
scores: Vec<u32>,
}
impl Student {
fn new(name: &str, age: u8) -> Self {
Student {
name: name.to_string(),
age,
scores: Vec::new(),
}
}
fn add_score(&mut self, score: u32) {
self.scores.push(score);
}
fn average(&self) -> f64 {
if self.scores.is_empty() {
return 0.0;
}
let sum: u32 = self.scores.iter().sum();
sum as f64 / self.scores.len() as f64
}
fn grade(&self) -> char {
match self.average() as u32 {
90..=100 => 'A',
80..=89 => 'B',
70..=79 => 'C',
60..=69 => 'D',
_ => 'F',
}
}
fn summary(&self) -> String {
format!("{} ({} years old) Average: {:.1} Level: {}", self.name, self.age, self.average(), self.grade())
}
}
fn main() {
let mut alice = Student::new("Alice", 20);
alice.add_score(95);
alice.add_score(88);
alice.add_score(92);
println!("{}", alice.summary());
let mut bob = Student::new("Bob", 21);
bob.add_score(72);
bob.add_score(65);
bob.add_score(58);
println!("{}", bob.summary());
let mut charlie = Student {
name: String::from("Charlie"),
..alice.clone()
};
charlie.name = String::from("Charlie");
charlie.add_score(100);
println!("{}", charlie.summary());
let students = [&alice, &bob, &charlie];
println!("\n=== Class Rankings ===");
let mut ranked: Vec<_> = students.iter().collect();
ranked.sort_by(|a, b| b.average().partial_cmp(&a.average()).unwrap());
for (i, s) in ranked.iter().enumerate() {
println!("#{}: {}", i + 1, s.summary());
}
}
Resultado:
Alice (20 years old) Average: 91.7 Level: A
Bob (21 years old) Average: 65.0 Level: D
Charlie (20 years old) Average: 93.8 Level: A
=== Class Rankings ===
#1: Charlie (20 years old) Average: 93.8 Level: A
#2: Alice (20 years old) Average: 91.7 Level: A
#3: Bob (21 years old) Average: 65.0 Level: D
Este exemplo combina o uso da função de associação
new(), dos métodosadd_score()/average()/grade()e das sintaxes de atualização..alice.clone()e#[derive(Debug, Clone)]. As estruturas são as principais ferramentas para organizar dados e métodos relacionados.
❓ Perguntas Frequentes
P: Como é tratada a propriedade dos campos de uma estrutura? R: A própria estrutura segue as regras de propriedade. Se um campo da estrutura for um
String, a propriedade desse campo é transferida quando a estrutura é movida. Se o campo for emprestado (uma referência), a estrutura deve ser anotada com um tempo de vida.
P: Quando se deve usar métodos (com
&self) e funções associadas (sem&self)? R: Use métodos quando precisar manipular dados de instância e use funções associadas quando não precisar desses dados. Os métodos são chamados usandoobj.method(), enquanto as funções associadas são chamadas usandoType::function(). As funções associadas são frequentemente usadas em “construtores” (comoString::from).
P: Qual é a diferença entre &self e self? R: &self é uma referência (sem transferência de propriedade), enquanto self consome a instância (transferência de propriedade). A maioria dos métodos usa
&selfporque precisa apenas visualizar os dados e não precisa retê-los.selfé usado em cenários em que a instância é transformada ou destruída.
P: Uma estrutura pode armazenar uma referência? R: Sim, mas é preciso especificar seu tempo de vida. Por exemplo,
struct S<'a> { r: &'a str }. Sem uma especificação de tempo de vida, o compilador não sabe por quanto tempo a referência permanecerá ativa (veja a Lição 11).
P: O que #[derive(Debug)] faz? R: Ele implementa automaticamente o trait Debug para estruturas. O trait
Debugpermite que as estruturas sejam impressas no formato{:?}. O Rust também oferece suporte aderiveoutras traits, comoClone,Copy,PartialEqe assim por diante.
📖 Resumo
- Estruturas nomeadas são o tipo de estrutura mais comumente utilizado, com nomes de campos bem definidos
- Estruturas de tuplas são adequadas para cenários que envolvem “apenas um conjunto de valores” (RGB, coordenadas)
- Estruturas unitárias não possuem campos e são utilizadas para anotação de tipos.
implAdicionar métodos (&self) e funções associadas (nenhuma&self) à estrutura- Inicialização da abreviação do campo: omitir os dois pontos quando o nome da variável for igual ao nome do campo
- Atualização de sintaxe
..other: Copiar os campos restantes de uma instância #[derive(Debug)]Tornando estruturas imprimíveis
📝 Exercícios
- Dificuldade ⭐: Defina uma estrutura
Bookque contenhatitle: String,author: Stringeyear: u32. Crie duas instâncias e imprima o título e o autor. - Dificuldade ⭐⭐: Adicione um método
fn perimeter(&self) -> u32aoRectanglepara calcular o perímetro. Adicione uma função associadafn from_width(w: u32) -> Rectanglepara criar um retângulo com largura e altura iguais. - Dificuldade ⭐⭐⭐: Defina uma estrutura de tupla
Distance(f64, f64)para representar distâncias (em metros e centímetros). Implemente um métodofn to_meters(&self) -> f64que retorne o número total de metros (por exemplo,Distance(1, 50)retorna1.50).