Capítulo 09: Structs, Tuplas e Métodos com impl
Capítulo 09: Structs, Tuplas e Métodos com impl
Especialização em Sistemas com Rust • Rust 2021 Edition • Ownership, Concorrência Segura e Sistemas de Alta Performance
🗺️ Mapa Conceitual da Modelagem Orientada a Dados com Structs e Métodos
flowchart TD
Modelagem["Modelagem de Domínio em Rust"] --> Variedades["Variedades de Structs"]
Modelagem --> Implementacao["Comportamento com Blocos impl"]
subgraph STRUCT_KINDS["As 3 Variedades de Structs"]
Classic["Classic Struct: Campos Nomeados (struct Servidor { ip: String })"]
TupleStruct["Tuple Struct: Newtype Pattern (struct Porta(u16))"]
UnitStruct["Unit Struct: Zero-Sized Type Marcador (struct ProtocoloTLS)"]
end
subgraph IMPL_METHODS["Métodos e Funções Associadas"]
AssociatedFn["Função Associada (Construtor): fn new() -> Self"]
ReadMethod["Método de Leitura: fn status(&self) -> &str"]
MutMethod["Método de Escrita: fn atualizar(&mut self)"]
ConsumeMethod["Método de Consumo: fn encerrar(self) (Drop automático)"]
end
Variedades --- STRUCT_KINDS
Implementacao --- IMPL_METHODS
style Modelagem fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style Variedades fill:#fff3e0,stroke:#ff9800,stroke-width:2px
style Implementacao fill:#ede7f6,stroke:#7e57c2,stroke-width:2px
style IMPL_METHODS fill:#e8f5e9,stroke:#4caf50,stroke-width:2px
🏛️ 1. O Modelo Orientado a Dados em Rust (Sem Herança)
Diferente de linguagens clássicas orientadas a objetos (como Java, C# ou C++), o Rust não possui herança de classes. Em vez disso, o design de software em Rust adota a Composição Pura e a Separação Rígida entre Dados e Comportamento:
- Dados: São modelados estritamente através de
structseenums. - Comportamento: É anexado aos dados através de blocos de implementação
implou via contratos de interface chamadostraits.
Essa abordagem elimina os conhecidos problemas de herança profunda (como o Fragile Base Class Problem e herança múltipla ambígua), resultando em código modular, previsível e com layout de memória otimizado.
⚙️ 2. As Três Formas de Structs em Rust
2.1 Classic Structs (Campos Nomeados)
A forma mais comum para modelar entidades complexas de negócio:
#[derive(Debug, Clone)]
struct ConexaoBanco {
host: String,
porta: u16,
pool_maximo: u32,
ativo: bool,
}
2.2 Tuple Structs e o Newtype Pattern
Tuple structs nomeiam uma tupla inteira sem dar nomes aos campos individuais. São muito utilizadas para criar tipos nominais fortes (Newtype Pattern), impedindo que parâmetros do mesmo tipo primitivo sejam passados na ordem errada:
struct LarguraPixels(pub u32);
struct AlturaPixels(pub u32);
// O compilador impede passar Largura no lugar de Altura, embora ambos sejam u32!
fn redimensionar_canvas(w: LarguraPixels, h: AlturaPixels) {
println!("Canvas: {}x{} px", w.0, h.0);
}
2.3 Unit Structs (Zero-Sized Types - ZST)
Structs sem nenhum campo. Não ocupam nenhum byte na memória (size_of == 0), sendo utilizadas pelo compilador como marcadores de estado em tempo de compilação (State Pattern estático) e traits de sincronização.
🔨 3. O Bloco impl: Métodos e Funções Associadas
Dentro de um bloco impl, você define tanto funções associadas quanto métodos:
struct ServidorCluster {
identificador: String,
carga_cpu_pct: f32,
}
impl ServidorCluster {
// 1. Função Associada (Construtor Idiomático 'new')
// Não recebe 'self'. É invocada via 'ServidorCluster::new(...)'.
fn new(id: &str) -> Self {
Self {
identificador: String::from(id),
carga_cpu_pct: 0.0,
}
}
// 2. Método de Leitura Imutável (&self)
// Permite que qualquer chamador inspecione o estado sem mutação.
fn esta_sobrecarregado(&self) -> bool {
self.carga_cpu_pct > 80.0
}
// 3. Método de Escrita Mutável (&mut self)
// Exige empréstimo mutável exclusivo da instância.
fn atualizar_telemetria(&mut self, nova_carga: f32) {
self.carga_cpu_pct = nova_carga;
}
// 4. Método que Assume Ownership (self por valor)
// Consome e destrói a instância ao finalizar a operação.
fn descomissionar(self) {
println!("Descomissionando servidor: {}", self.identificador);
// 'self' sai de escopo e sua memória é desalocada via Drop!
}
}
🧩 4. Struct Update Syntax (..instancia)
Ao instanciar uma nova struct aproveitando a maioria dos campos de outra já existente, utiliza-se a sintaxe funcional ..:
let config_padrao = ConexaoBanco {
host: String::from("localhost"),
porta: 5432,
pool_maximo: 10,
ativo: true,
};
// Cria uma nova conexão sobrescrevendo apenas o host
let config_producao = ConexaoBanco {
host: String::from("db.producao.cloud.internal"),
..config_padrao // Copia os demais campos (porta, pool_maximo, ativo)
};
⚖️ 5. Matriz Comparativa dos Tipos de self em Métodos
| Assinatura | Empréstimo | Permite Mutação? | Efeito sobre o Chamador |
|---|---|---|---|
&self |
Empréstimo Imutável | Não (Apenas leitura) | A instância continua disponível e intacta |
&mut self |
Empréstimo Mutável | SIM (Mutação in-place) | A instância continua disponível com novo estado |
self |
Move (Posse Total) | Sim (se mut self) |
A instância é consumida e destruída |
⚠️ 6. Armadilhas Comuns e Mensagens do Compilador
- Esquecer
&mut selfao Modificar Campos:fn registrar(&self) { self.carga_cpu_pct = 50.0; // ERRO: cannot assign to `self.carga_cpu_pct`, which is behind a `&` reference }Correção: Altere a assinatura para
&mut selfe garanta que a variável instanciada pelo chamador seja mutável (let mut s = ...). - Tentar Usar a Instância após um Método que Recebe
self: Se um método receberselfpor valor, a instância é movida para dentro do método. Tentar acessá-la novamente depois gerará o errouse of moved value.
🛡️ 7. Boas Práticas e Recomendações de Engenharia
- Implemente Sempre
Debug: Anote#[derive(Debug)]em suas structs para viabilizar logging e depuração formatada com{:?}. - Utilize o Newtype Pattern: Não use primitivos brutos para grandezas físicas ou identificadores sensíveis (como
UserID(u64)eProductID(u64)). O compilador garantirá que umUserIDnunca seja acidentalmente passado para um endpoint que esperaProductID.
📋 8. Cheat-Sheet: Resumo Executivo
struct Ponto { x: i32, y: i32 }
impl Ponto {
fn new(x: i32, y: i32) -> Self { Self { x, y } }
fn distancia_origem(&self) -> f64 { ((self.x*self.x + self.y*self.y) as f64).sqrt() }
fn mover(&mut self, dx: i32, dy: i32) { self.x += dx; self.y += dy; }
}