Rust: Traços do Rust: Definindo Comportamentos Compartilhados
Última atualização: 2026-08-26
Um trait é o equivalente a uma “interface” no Rust — ele define um conjunto de assinaturas de métodos, e diferentes tipos podem implementar o mesmo trait, compartilhando, assim, o mesmo contrato de comportamento.
Se um tipo concreto é “o que essa coisa é”, então uma característica é “o que essa coisa pode fazer”. Veja a porta USB-C, por exemplo — seja um celular, um laptop ou um tablet, desde que seja compatível com USB-C, você pode conectá-lo para carregar. O funcionamento interno desses dispositivos pode variar bastante, mas o comportamento que eles prometem aos usuários é consistente.
1. O que você vai aprender
- Sintaxe para definir uma característica
trait Name { fn method(&self); }e implementar uma característica para um tipo - Método padrão — Forneça uma implementação padrão na definição do trait; quem implementar o trait pode optar por sobrescrevê-la
- Característica derivável:
#[derive(Debug, Clone, Copy, PartialEq)] - Traços como parâmetros:
impl TraitSintaxe e restrições genéricasT: Trait - objeto de característica
dyn Trait— Distribuição dinâmica em tempo de execução - Herança de traços (supertraço): um traço herda métodos de outro traço
2. Diagramas conceituais
O diagrama Mermaid a seguir ilustra a cadeia completa de relações de uma característica — desde sua definição até sua implementação para vários tipos e, finalmente, até sua invocação polimórfica:
graph LR
A["trait UsbCCharge"] -->|Defining a Behavioral Contract| B["fn charge(&self)"]
A --> C["fn voltage(&self) -> u32"]
A --> D["fn charge_time(&self) -> String"]
B --> E["impl for Phone<br/>charge: 18W"]
B --> F["impl for Laptop<br/>charge: 65W"]
B --> G["impl for Tablet<br/>charge: 30W"]
E --> H["phone.charge()"]
F --> I["laptop.charge()"]
G --> J["tablet.charge()"]
H --> K["Polymorphic Call<br/>The Same trait Interface<br/>Different types of behavior vary"]
I --> K
J --> K
3. A história do carregador universal
(1) O incômodo: cada aparelho tem seu próprio método de recarga
Leo (Leo) é arquiteto em uma empresa de eletrônicos. A linha de produtos da empresa está se expandindo para incluir celulares, laptops e tablets, e cada dispositivo possui sua própria lógica de carregamento.
No início, ele escreveu o código de carregamento separadamente para cada dispositivo:
struct Phone;
struct Laptop;
struct Tablet;
impl Phone {
fn charge(&self) {
println!("Phone: charging via USB-C at 18W");
}
}
impl Laptop {
fn charge(&self) {
println!("Laptop: charging via USB-C at 65W");
}
}
impl Tablet {
fn charge(&self) {
println!("Tablet: charging via USB-C at 30W");
}
}
fn main() {
let phone = Phone;
let laptop = Laptop;
let tablet = Tablet;
phone.charge();
laptop.charge();
tablet.charge();
}
Cada uma das três estruturas possui um método
charge— elas compartilham o mesmo nome e a mesma assinatura, mas não há “compatibilidade” entre elas. Se você quisesse escrever uma função de “estação de recarga universal” para recarregar qualquer dispositivo, isso não seria possível. Cada tipo de dispositivo é independente, sem uma camada abstrata comum.
(2) Requisitos mais complexos: carregamento em lote
O gerente de produto solicitou o desenvolvimento de uma “estação de recarga universal” capaz de recarregar vários tipos diferentes de dispositivos simultaneamente:
// This code cannot be compiled.——Unknown parameter type
// fn charge_all_devices(devices: ???) {
// for device in devices {
// device.charge();
// }
// }
Sem um trait, charge_all_devices não pode aceitar coleções de tipos mistos. É preciso sobrecarregar uma função para cada tipo ou desistir.
(3) A abordagem das características (traits) do Rust
// Define a trait: a shared behavior contract
trait UsbCCharge {
fn charge(&self);
fn voltage(&self) -> u32 { 18 } // Default method with default voltage
}
struct Phone;
struct Laptop;
struct Tablet;
impl UsbCCharge for Phone {
fn charge(&self) {
println!("Phone: charging via USB-C at {}W", self.voltage());
}
// voltage() uses the default (18W)
}
impl UsbCCharge for Laptop {
fn charge(&self) {
println!("Laptop: charging via USB-C at {}W", self.voltage());
}
fn voltage(&self) -> u32 { 65 } // Override default
}
impl UsbCCharge for Tablet {
fn charge(&self) {
println!("Tablet: charging via USB-C at {}W", self.voltage());
}
fn voltage(&self) -> u32 { 30 } // Override default
}
// Generic function: accepts any type that implements UsbCCharge
fn charge_device<T: UsbCCharge>(device: &T) {
device.charge();
}
fn main() {
charge_device(&Phone);
charge_device(&Laptop);
charge_device(&Tablet);
}
Resultado:
Phone: charging via USB-C at 18W
Laptop: charging via USB-C at 65W
Tablet: charging via USB-C at 30W
trait UsbCChargedefine um “contrato de comportamento de carregamento”. Qualquer tipo que implemente essa característica pode ser aceito porcharge_device.voltage()possui uma implementação padrão (18 W), mas Laptop e Tablet optam por sobrescrevê-la. Esse é o valor central de uma característica — definir um comportamento comum, ao mesmo tempo em que permite implementações diferenciadas.
4. Conceitos fundamentais
(1) Visão geral do sistema de características
graph TB
A[Rust Trait System] --> B[Trait Definition]
A --> C[Implementing Traits]
A --> D[Derivable Traits]
A --> E[Trait as Parameters]
A --> F[Trait Objects]
A --> G[Trait Inheritance]
B --> B1["trait Name { fn method(&self); }"]
C --> C1["impl TraitName for MyType { ... }"]
C --> C2["Default methods in trait"]
D --> D1["#[derive(Debug, Clone, Copy, PartialEq)]"]
D --> D2["Compiler auto-generates implementation"]
E --> E1["fn foo(x: impl Trait)"]
E --> E2["fn foo<T: Trait>(x: T)"]
E --> E3["fn foo<T>(x: T) where T: Trait"]
F --> F1["Box<dyn Trait>"]
F --> F2["Runtime dispatch (vtable)"]
G --> G1["trait A: SuperTrait { }"]
G --> G2["Inherits methods from SuperTrait"]
(2) Distribuição estática x Distribuição dinâmica
| Recurso | Restrições genéricas T: Trait / impl Trait |
Instâncias de traços dyn Trait |
|---|---|---|
| Momento da distribuição | Tempo de compilação (distribuição estática) | Tempo de execução (distribuição dinâmica) |
| Implementação | Singleton — Gerar código separado para cada tipo | Tabela virtual (vtable) — Chamada indireta por meio de ponteiros |
| Desempenho | Sem sobrecarga (pode ser inlinado) | Sobrecarga de chamada indireta |
| Tamanho do binário | Maior (uma cópia por tipo) | Menor (uma cópia do código) |
| Requisitos de tipo | O tipo específico é determinado no momento da chamada | Os tipos podem variar, desde que implementem a mesma característica |
| Casos de uso | Sensíveis ao desempenho, com tipos conhecidos em tempo de compilação | Requer coleções heterogêneas, com tipos determinados em tempo de execução |
(3) Características deriváveis comumente utilizadas
| Característica | Função | Comportamento gerado automaticamente |
|---|---|---|
Debug |
Saída formatada {:?} |
Gerar saída de depuração; exibir nomes e valores dos campos da estrutura |
Clone |
Copiar explicitamente .clone() |
Gerar o método clone, copiando campo por campo |
Copy |
Cópia implícita (cópia bit a bit) | A propriedade não é transferida durante a atribuição; em vez disso, o valor é copiado |
PartialEq |
Comparação de igualdade == / != |
Gerar o método eq, comparar campo por campo |
Eq |
Equivalência total (relação de equivalência matemática) | Com base em PartialEq, com a garantia adicional de reflexividade |
Hash |
Cálculo de hash | Método para gerar hash, calculando os valores de hash campo por campo |
Default |
Valor padrão | Gerar o método default, utilizando o valor padrão para cada campo |
(4) Referência rápida para a combinação de restrições de características
| Tipo de restrição | Sintaxe | Casos de uso | Exemplo |
|---|---|---|---|
| Restrição única em linha | fn foo<T: Trait>(x: T) |
Restrição única simples | fn charge<T: UsbCCharge>(d: &T) |
| Restrições múltiplas em linha | fn foo<T: Trait1 + Trait2>(x: T) |
Restrições múltiplas | fn describe<T: Debug + UsbCCharge>(d: &T) |
| Cláusula WHERE | fn foo<T>(x: T) where T: Trait |
Restrições complexas, parâmetros multitipo | where T: UsbCCharge, U: Debug |
| Traço impl | fn foo(x: impl Trait) |
Sintaxe simplificada | fn plug(d: &impl USBDevice) |
| Traço impl + | fn foo(x: impl Trait1 + Trait2) |
Simples e restrito | fn describe(d: &(impl USBDevice + Debug)) |
5. Exemplos de traços
▶ Exemplo 1: Definições de características e métodos padrão — Carregador de aparelho (Dificuldade ⭐)
// ============================================
// Trait definition with default methods
// ============================================
// Define a trait: any device that can charge via USB-C
trait UsbCCharge {
// Required method: must be implemented
fn charge(&self);
// Default method: implementor MAY override
fn voltage(&self) -> u32 {
18 // Default: standard USB-C 18W
}
// Another default method using self.voltage()
fn charge_time(&self, battery_capacity_mah: u32) -> String {
let hours = battery_capacity_mah as f64 / (self.voltage() as f64 * 1000.0 / 5.0);
format!("{:.1} hours to full charge", hours)
}
}
struct Phone;
struct Laptop;
struct Tablet;
impl UsbCCharge for Phone {
fn charge(&self) {
// Uses the default voltage() -> 18W
println!("Phone: charging at {}W (standard speed)", self.voltage());
}
// voltage() uses default, charge_time() uses default
}
impl UsbCCharge for Laptop {
fn charge(&self) {
println!("Laptop: charging at {}W (fast charging)", self.voltage());
}
fn voltage(&self) -> u32 {
65 // Laptop needs more power
}
}
impl UsbCCharge for Tablet {
fn charge(&self) {
println!("Tablet: charging at {}W (medium speed)", self.voltage());
}
fn voltage(&self) -> u32 {
30
}
}
fn main() {
let phone = Phone;
let laptop = Laptop;
let tablet = Tablet;
phone.charge();
println!(" -> {}", phone.charge_time(3000));
laptop.charge();
println!(" -> {}", laptop.charge_time(6000));
tablet.charge();
println!(" -> {}", tablet.charge_time(5000));
}
Resultado:
Phone: charging at 18W (standard speed)
-> 0.8 hours to full charge
Laptop: charging at 65W (fast charging)
-> 0.5 hours to full charge
Tablet: charging at 30W (medium speed)
-> 0.8 hours to full charge
Na característica
UsbCCharge,charge()é um método obrigatório — todo implementador deve fornecê-lo.voltage()echarge_time()são métodos padrão — eles fornecem uma implementação padrão, e os implementadores podem optar por sobrescrevê-los ou usá-los como estão. O Phone implementa apenascharge()e usa as implementações padrão para os outros dois métodos; o Laptop sobrescrevevoltage()e usa as implementações padrão para o restante.
▶ Exemplo 2: Derivação de traços — Depuração, clonagem e comparação (Dificuldade ⭐⭐)
// ============================================
// Derivable traits: Debug, Clone, Copy, PartialEq
// ============================================
// Without deriving, none of these operations would work
#[derive(Debug, Clone, Copy, PartialEq)]
struct ChargerSpec {
brand: &'static str,
watts: u32,
usb_c: bool,
}
// PartialEq is needed for this custom type
#[derive(Debug, Clone, PartialEq)]
struct Device {
name: String,
required_watts: u32,
}
fn main() {
// --- Debug: pretty-print with {:?} ---
let spec1 = ChargerSpec { brand: "Anker", watts: 65, usb_c: true };
println!("Debug: {:?}", spec1);
println!("Pretty: {:#?}", spec1);
// --- Clone: explicit copy ---
let spec2 = spec1.clone(); // spec1 is still valid
println!("Cloned: {:?}", spec2);
// --- Copy: implicit copy (only if Copy is derived) ---
let spec3 = spec1; // spec1 is STILL valid because ChargerSpec is Copy!
println!("Copied (implicit): {:?}", spec3);
println!("Original still valid: {:?}", spec1); // Works!
// --- PartialEq: equality comparison ---
let spec_a = ChargerSpec { brand: "Anker", watts: 65, usb_c: true };
let spec_b = ChargerSpec { brand: "Anker", watts: 65, usb_c: true };
let spec_c = ChargerSpec { brand: "Baseus", watts: 65, usb_c: true };
println!("spec_a == spec_b: {}", spec_a == spec_b); // true
println!("spec_a == spec_c: {}", spec_a == spec_c); // false (brand differs)
println!("spec_a != spec_c: {}", spec_a != spec_c); // true
// --- Practical: filtering devices ---
let phone = Device {
name: String::from("Phone"),
required_watts: 18,
};
let laptop = Device {
name: String::from("Laptop"),
required_watts: 65,
};
// Clone a device
let phone_backup = phone.clone();
println!("\nPhone backup: {:?}", phone_backup);
// Compare devices by required_watts
let charger_watts = 65;
let compatible = vec![phone, laptop]
.iter()
.filter(|d| d.required_watts <= charger_watts)
.collect::<Vec<_>>();
println!("Compatible devices (<= {}W): {:?}", charger_watts, compatible);
}
Resultado:
Debug: ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
Pretty: ChargerSpec {
brand: "Anker",
watts: 65,
usb_c: true,
}
Cloned: ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
Copied (implicit): ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
Original still valid: ChargerSpec { brand: "Anker", watts: 65, usb_c: true }
spec_a == spec_b: true
spec_a == spec_c: false
spec_a != spec_c: true
Phone backup: Device { name: "Phone", required_watts: 18 }
Compatible devices (<= 65W): [Device { name: "Phone", required_watts: 18 }, Device { name: "Laptop", required_watts: 65 }]
Com apenas uma linha de código
#[derive(Debug, Clone, Copy, PartialEq)], o compilador do Rust gerou automaticamente implementações para quatro traitsChargerSpec. Observe a diferença entreCopyeClone:Clonerequer uma chamada explícita a.clone(), enquantoCopyé uma cópia bit a bit implícita (a atribuição não envolve uma movimentação).Devicenão possuiCopyporque o tipoStringnão implementaCopy(ele aloca memória na pilha).
▶ Exemplo 3: Traços como parâmetros — impl Trait e restrições genéricas (Dificuldade ⭐⭐)
// ============================================
// Trait as parameter: impl Trait, generic bounds, where clause
// ============================================
trait USBDevice {
fn device_name(&self) -> &str;
fn power_draw(&self) -> u32;
}
struct Mouse;
struct Keyboard;
struct Webcam;
impl USBDevice for Mouse {
fn device_name(&self) -> &str { "Mouse" }
fn power_draw(&self) -> u32 { 2 }
}
impl USBDevice for Keyboard {
fn device_name(&self) -> &str { "Keyboard" }
fn power_draw(&self) -> u32 { 3 }
}
impl USBDevice for Webcam {
fn device_name(&self) -> &str { "Webcam" }
fn power_draw(&self) -> u32 { 5 }
}
// --- Style 1: impl Trait (sugar for simple cases) ---
fn plug_device(device: &impl USBDevice) {
println!("[Plugged] {} (draws {}W)", device.device_name(), device.power_draw());
}
// --- Style 2: Generic bound T: Trait (explicit type parameter) ---
fn print_device_spec<T: USBDevice>(device: &T) {
println!("[Spec] {} - Power: {}W", device.device_name(), device.power_draw());
}
// --- Style 3: where clause (best for complex bounds) ---
fn check_compatible<T>(device: &T, max_power: u32) -> bool
where
T: USBDevice,
{
device.power_draw() <= max_power
}
// --- Style 4: Multiple trait bounds ---
use std::fmt::Debug;
fn describe_device(device: &(impl USBDevice + Debug)) {
println!("[Debug] Device: {:?}", device);
}
fn main() {
let mouse = Mouse;
let keyboard = Keyboard;
let webcam = Webcam;
// impl Trait syntax
plug_device(&mouse);
plug_device(&keyboard);
// Generic bound syntax
print_device_spec(&webcam);
// where clause
println!("\n--- Compatibility Check (max 3W) ---");
println!("Mouse compatible: {}", check_compatible(&mouse, 3));
println!("Keyboard compatible: {}", check_compatible(&keyboard, 3));
println!("Webcam compatible: {}", check_compatible(&webcam, 3));
// Calculate total power draw for a list
let devices: Vec<&dyn USBDevice> = vec![&mouse, &keyboard, &webcam];
let total_power: u32 = devices.iter().map(|d| d.power_draw()).sum();
println!("\nTotal power draw: {}W / 15W budget", total_power);
}
Resultado:
[Plugged] Mouse (draws 2W)
[Plugged] Keyboard (draws 3W)
[Spec] Webcam - Power: 5W
--- Compatibility Check (max 3W) ---
Mouse compatible: true
Keyboard compatible: true
Webcam compatible: false
Total power draw: 10W / 15W budget
Cada um dos quatro estilos de parâmetros de traço tem seu próprio caso de uso:
impl Trait(conciso, adequado para um único traço),T: Trait(nomes explícitos de parâmetros de tipo, adequado para tipos de referência),where T: Trait(melhor legibilidade quando há múltiplas restrições),impl Trait + AnotherTrait(múltiplas restrições. Observe queVec<&dyn USBDevice>é um objeto de traço (veja o próximo exemplo) — usado aqui para armazenar referências de diferentes tipos.)
▶ Exemplo 4: O objeto de característica dyn Trait e a herança de características (Dificuldade: ⭐⭐⭐)
// ============================================
// dyn Trait (runtime dispatch) + Trait inheritance
// ============================================
use std::fmt::Debug;
// --- Super trait (trait inheritance) ---
// AnyDevice "inherits" from Debug: to implement AnyDevice,
// a type must also implement Debug
trait AnyDevice: Debug {
fn model_name(&self) -> &str;
}
// UsbDevice extends AnyDevice: it requires Debug + AnyDevice
trait UsbDevice: AnyDevice {
fn usb_version(&self) -> &str;
fn transfer_speed(&self) -> &str;
}
// --- Implement the trait hierarchy ---
#[derive(Debug)]
struct FlashDrive {
name: String,
capacity_gb: u32,
}
impl AnyDevice for FlashDrive {
fn model_name(&self) -> &str {
&self.name
}
}
impl UsbDevice for FlashDrive {
fn usb_version(&self) -> &str {
"USB 3.2 Gen 2"
}
fn transfer_speed(&self) -> &str {
"10 Gbps"
}
}
#[derive(Debug)]
struct ExternalSSD {
name: String,
capacity_tb: f64,
}
impl AnyDevice for ExternalSSD {
fn model_name(&self) -> &str {
&self.name
}
}
impl UsbDevice for ExternalSSD {
fn usb_version(&self) -> &str {
"USB 3.2 Gen 2x2"
}
fn transfer_speed(&self) -> &str {
"20 Gbps"
}
}
// --- Function using trait objects ---
// Accept a heterogeneous collection of UsbDevice implementors
fn list_devices(devices: &[Box<dyn UsbDevice>]) {
for (i, device) in devices.iter().enumerate() {
println!(
"Device #{}: {} ({} - {}, Debug: {:?})",
i + 1,
device.model_name(),
device.usb_version(),
device.transfer_speed(),
device,
);
}
}
// --- Function returning a trait object ---
fn make_device(device_type: &str) -> Option<Box<dyn UsbDevice>> {
match device_type {
"flash" => Some(Box::new(FlashDrive {
name: String::from("SanDisk 128GB"),
capacity_gb: 128,
})),
"ssd" => Some(Box::new(ExternalSSD {
name: String::from("Samsung T7 1TB"),
capacity_tb: 1.0,
})),
_ => None,
}
}
fn main() {
// Heterogeneous collection: different types, same trait
let drive1 = Box::new(FlashDrive {
name: String::from("Kingston 64GB"),
capacity_gb: 64,
});
let drive2 = Box::new(ExternalSSD {
name: String::from("WD My Passport 2TB"),
capacity_tb: 2.0,
});
let all_devices: Vec<Box<dyn UsbDevice>> = vec![drive1, drive2];
println!("--- Connected Devices ---");
list_devices(&all_devices);
// Factory function returning trait objects
println!("\n--- Device Factory ---");
if let Some(device) = make_device("ssd") {
println!("Created: {} ({} - {})", device.model_name(), device.usb_version(), device.transfer_speed());
}
// Trait inheritance in action: UsbDevice requires Debug
// so we can use both {:?} and trait methods
println!("\n--- Debug via Super Trait ---");
let flash = FlashDrive {
name: String::from("Lexar 32GB"),
capacity_gb: 32,
};
// flash has Debug (from AnyDevice: Debug), AnyDevice, and UsbDevice
println!("Debug: {:?}", flash);
println!("Model: {}", flash.model_name());
println!("USB: {}", flash.usb_version());
}
Saída:
--- Connected Devices ---
Device #1: Kingston 64GB (USB 3.2 Gen 2 - 10 Gbps, Debug: FlashDrive { name: "Kingston 64GB", capacity_gb: 64 })
Device #2: WD My Passport 2TB (USB 3.2 Gen 2x2 - 20 Gbps, Debug: ExternalSSD { name: "WD My Passport 2TB", capacity_tb: 2.0 })
--- Device Factory ---
Created: Samsung T7 1TB (USB 3.2 Gen 2x2 - 20 Gbps)
--- Debug via Super Trait ---
Debug: FlashDrive { name: "Lexar 32GB", capacity_gb: 32 }
Model: Lexar 32GB
USB: USB 3.2 Gen 2
Herança de características (supercaracterística):
trait AnyDevice: Debugsignifica que “qualquer tipo que implementeAnyDevicetambém deve implementarDebug”.trait UsbDevice: AnyDeviceé adicionado em uma camada superior — formando uma hierarquia de traços de três níveis. Tipos que implementamUsbDevicetambém devem implementar todos os métodos deDebug+AnyDevice+UsbDevice.Objeto de Trait
dyn Trait: UsandoBox<dyn UsbDevice>, é possível armazenar objetos de diferentes tipos que implementam o mesmo trait em uma única coleção. As chamadas de método são despachadas em tempo de execução por meio da tabela virtual (vtable) — isso acarreta uma ligeira sobrecarga de desempenho, mas oferece enorme flexibilidade. A funçãomake_device, que retornaOption<Box<dyn UsbDevice>>, incorpora o “Padrão de Fábrica”.
▶ Exemplo 5: Exercício abrangente — Cálculo da área de figuras geométricas (Dificuldade ⭐⭐⭐)
// ============================================
// Comprehensive Example:Trait + Generic Constraints + dyn Trait
// ============================================
use std::fmt::Debug;
trait Shape: Debug {
fn area(&self) -> f64;
fn name(&self) -> &str;
fn describe(&self) -> String {
format!("{}: Area = {:.2}", self.name(), self.area())
}
}
#[derive(Debug)]
struct Circle { radius: f64 }
#[derive(Debug)]
struct Rectangle { width: f64, height: f64 }
#[derive(Debug)]
struct Triangle { base: f64, height: f64 }
impl Shape for Circle {
fn area(&self) -> f64 { std::f64::consts::PI * self.radius * self.radius }
fn name(&self) -> &str { "Circular" }
}
impl Shape for Rectangle {
fn area(&self) -> f64 { self.width * self.height }
fn name(&self) -> &str { "Rectangle" }
}
impl Shape for Triangle {
fn area(&self) -> f64 { 0.5 * self.base * self.height }
fn name(&self) -> &str { "Triangle" }
}
fn total_area(shapes: &[Box<dyn Shape>]) -> f64 {
shapes.iter().map(|s| s.area()).sum()
}
fn largest<T: Shape>(shapes: &[T]) -> &T {
shapes.iter().max_by(|a, b| a.area().partial_cmp(&b.area()).unwrap()).unwrap()
}
fn print_all(shapes: &[Box<dyn Shape>]) {
for s in shapes {
println!(" {}", s.describe());
}
}
fn main() {
let shapes_static: Vec<&dyn Shape> = vec![
&Circle { radius: 5.0 },
&Rectangle { width: 4.0, height: 6.0 },
&Triangle { base: 3.0, height: 8.0 },
];
println!("=== Static Reference Traversal ===");
for s in &shapes_static {
println!(" {}", s.describe());
}
let shapes_dynamic: Vec<Box<dyn Shape>> = vec![
Box::new(Circle { radius: 10.0 }),
Box::new(Rectangle { width: 3.0, height: 7.0 }),
Box::new(Triangle { base: 6.0, height: 4.0 }),
];
println!("\n=== Dynamic Distribution ===");
print_all(&shapes_dynamic);
println!("Total Area: {:.2}", total_area(&shapes_dynamic));
let homogenous = vec![
Circle { radius: 3.0 },
Circle { radius: 7.0 },
Circle { radius: 5.0 },
];
let biggest = largest(&homogenous);
println!("\nLargest Circle: {}", biggest.describe());
}
Resultado:
=== Static Reference Traversal ===
Circular: Area = 78.54
Rectangle: Area = 24.00
Triangle: Area = 12.00
=== Dynamic Distribution ===
Circular: Area = 314.16
Rectangle: Area = 21.00
Triangle: Area = 12.00
Total Area: 347.16
Largest Circle: Circular: Area = 153.94
A mesma característica
Shapeé utilizada de três maneiras:&dyn Shapepara referências estáticas a fatias,Box<dyn Shape>para distribuição dinâmica de coleções eT: Shapepara determinar o valor máximo como uma restrição genérica.describeé o método padrão da característica, que todos os implementadores herdam automaticamente.
❓ Perguntas Frequentes
P: Qual é a diferença entre
impl Traitedyn Trait? Quando cada um deve ser usado? R:impl Traitutiliza despacho estático em tempo de compilação, enquantodyn Traitutiliza despacho dinâmico em tempo de execução.impl Traitnão tem sobrecarga, mas exige que o tipo seja determinado em tempo de compilação, tornando-o adequado para funções genéricas.dyn Traitacarreta sobrecarga devido a chamadas indiretas por meio da tabela virtual, mas suporta coleções heterogêneas (comoVec<Box<dyn Trait>>). Regra geral: useimpl Traitpara cenários em que o desempenho é essencial; usedyn Traitquando precisar armazenar tipos diferentes na mesma coleção.
P: Qual é a diferença entre
#[derive(Debug)]e a implementação manual? R:derivegera automaticamente código padrão, o que é adequado para estruturas simples com poucos campos. A implementação manualDebugpermite personalizar o formato de saída (por exemplo, ocultando campos de senha ou abreviando campos longos). O mesmo se aplica aClone,CopyePartialEq—deriveé adequado para o comportamento padrão de “processar campos um por um”, enquanto a implementação manual é recomendada para requisitos especiais.
P: Qual é exatamente a diferença entre
CopyeClone? R:Copyé uma cópia bit a bit implícita (a atribuição não transfere a propriedade), enquantoCloneé uma cópia profunda explícita (chama.clone()). Apenas tipos compostos por dados simples (em que todos os campos sãoCopy) podem serderive(Copy). Estruturas que contêm tipos alocados na pilha, comoStringeVec, só podem serderive(Clone), mas não podem serderive(Copy).
P: Qual é a diferença entre herança de traits e herança de classes em linguagens orientadas a objetos? R: A herança de traits no Rust é uma “herança de interface” (herança de assinaturas de métodos), e não uma “herança de implementação” (herança de implementações + estado). Na programação orientada a objetos, uma classe pai pode ter campos e implementações de métodos, que são herdados pela subclasse. No Rust, um trait só pode herdar assinaturas de métodos (pode ter implementações padrão, mas não pode ter campos). É também por isso que o Rust não apresenta o problema das “classes base frágeis” causado pela herança.
P: As notações
T: Trait1 + Trait2ewhere T: Trait1 + Trait2nas restrições de traços são iguais? R: Elas têm exatamente a mesma semântica; diferem apenas no estilo sintático. A cláusulawhereoferece melhor legibilidade em restrições complexas (vários parâmetros de tipo, com várias características por tipo). Para restrições simples, recomenda-se escrevê-las diretamente entre colchetes angulares. Essas são duas variantes de estilo específicas do Rust.
P: Qual é a finalidade de um trait vazio (trait marcador)? R: Um trait marcador não possui métodos; ele é usado para “identificar” um tipo, a fim de habilitar determinados comportamentos ou restrições do compilador. Por exemplo,
Sized(tamanho conhecido em tempo de compilação),Send(pode ser passado com segurança entre threads) eSync(pode ser compartilhado com segurança entre threads) são todos traços marcadores. Você pode definir seus próprios traços marcadores e combiná-los com restrições genéricas para restringir o comportamento dos tipos.
📖 Resumo
- Definição de característica
trait Name { fn method(&self); }declara um conjunto de contratos comportamentais; o bloco impl fornece implementações concretas desses contratos para tipos específicos. - Métodos padrão fornecem implementações padrão em um trait; quem implementa pode optar por sobrescrevê-los ou usá-los como estão.
- Traço derivado
#[derive(Debug, Clone, Copy, PartialEq)]permite que o compilador gere automaticamente implementações comuns de traços — abstração sem custo impl Trait(distribuição estática) edyn Trait(distribuição dinâmica) são duas formas de utilizar traços — a primeira não apresenta sobrecarga, enquanto a segunda oferece maior flexibilidade- Herança de características
trait A: SuperTraitEstabelecimento de uma hierarquia de características — os implementadores devem satisfazer todas as restrições de suas supercaracterísticas simultaneamente - Os traits são o mecanismo central do Rust para implementar a abstração de custo zero — eles são “interfaces” (genéricos) sem a sobrecarga das funções virtuais e também oferecem suporte ao polimorfismo opcional em tempo de execução, quando necessário (objetos trait).
📝 Exercícios
-
Dificuldade ⭐: Defina um
trait Drawable { fn draw(&self); }. Implemente essa característica nas duas estruturasCircleeSquare, de modo que cada uma imprima uma forma diferente. Escreva uma função genéricafn render<T: Drawable>(item: &T)que chamedraw. Na função principal, renderize um círculo e um quadrado, respectivamente. -
Dificuldade ⭐⭐: Defina uma característica
trait Summary { fn summarize(&self) -> String; fn author(&self) -> &str; }, em queauthor()é o método padrão que retorna"Anonymous". Implemente essa característica para as estruturasArticle { title: String, content: String }eTweet { username: String, text: String }. Crie dois artigos e dois tweets, coloque-os emVec<Box<dyn Summary>>e percorra-os para imprimir seus resumos. -
Dificuldade ⭐⭐⭐: Defina uma hierarquia de traços:
trait Vehicle: std::fmt::Debug { fn fuel_type(&self) -> &str; }, seguida detrait ElectricVehicle: Vehicle { fn battery_capacity_kwh(&self) -> f64; fn range_km(&self) -> f64; }. ImplementeElectricVehiclepara as estruturasTeslaModel3eNissanLeaf. Escreva uma funçãofn print_fleet(vehicles: &[Box<dyn ElectricVehicle>])para percorrer e imprimir as informações de cada carro. Na função principal, crie instâncias de ambos os tipos de carro e teste-as.