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


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:

100%
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:

RUST
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:

RUST
// 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

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:

TEXT 📖 Somente leitura
Phone: charging via USB-C at 18W
Laptop: charging via USB-C at 65W
Tablet: charging via USB-C at 30W

trait UsbCCharge define um “contrato de comportamento de carregamento”. Qualquer tipo que implemente essa característica pode ser aceito por charge_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

100%
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 ⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
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() e charge_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 apenas charge() e usa as implementações padrão para os outros dois métodos; o Laptop sobrescreve voltage() e usa as implementações padrão para o restante.


▶ Exemplo 2: Derivação de traços — Depuração, clonagem e comparação (Dificuldade ⭐⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
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 traits ChargerSpec. Observe a diferença entre Copy e Clone: Clone requer uma chamada explícita a .clone(), enquanto Copy é uma cópia bit a bit implícita (a atribuição não envolve uma movimentação). Device não possui Copy porque o tipo String não implementa Copy (ele aloca memória na pilha).


▶ Exemplo 3: Traços como parâmetros — impl Trait e restrições genéricas (Dificuldade ⭐⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
[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 que Vec<&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: ⭐⭐⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
--- 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: Debug significa que “qualquer tipo que implemente AnyDevice também deve implementar Debug”. trait UsbDevice: AnyDevice é adicionado em uma camada superior — formando uma hierarquia de traços de três níveis. Tipos que implementam UsbDevice também devem implementar todos os métodos de Debug + AnyDevice + UsbDevice.

Objeto de Trait dyn Trait: Usando Box<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ção make_device, que retorna Option<Box<dyn UsbDevice>>, incorpora o “Padrão de Fábrica”.


▶ Exemplo 5: Exercício abrangente — Cálculo da área de figuras geométricas (Dificuldade ⭐⭐⭐)

RUST
// ============================================
// 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:

TEXT 📖 Somente leitura
=== 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 Shape para referências estáticas a fatias, Box<dyn Shape> para distribuição dinâmica de coleções e T: Shape para 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 Trait e dyn Trait? Quando cada um deve ser usado? R: impl Trait utiliza despacho estático em tempo de compilação, enquanto dyn Trait utiliza despacho dinâmico em tempo de execução. impl Trait não tem sobrecarga, mas exige que o tipo seja determinado em tempo de compilação, tornando-o adequado para funções genéricas. dyn Trait acarreta sobrecarga devido a chamadas indiretas por meio da tabela virtual, mas suporta coleções heterogêneas (como Vec<Box<dyn Trait>>). Regra geral: use impl Trait para cenários em que o desempenho é essencial; use dyn Trait quando precisar armazenar tipos diferentes na mesma coleção.

P: Qual é a diferença entre #[derive(Debug)] e a implementação manual? R: derive gera automaticamente código padrão, o que é adequado para estruturas simples com poucos campos. A implementação manual Debug permite personalizar o formato de saída (por exemplo, ocultando campos de senha ou abreviando campos longos). O mesmo se aplica a Clone, Copy e PartialEqderive é 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 Copy e Clone? R: Copy é uma cópia bit a bit implícita (a atribuição não transfere a propriedade), enquanto Clone é uma cópia profunda explícita (chama .clone()). Apenas tipos compostos por dados simples (em que todos os campos são Copy) podem ser derive(Copy). Estruturas que contêm tipos alocados na pilha, como String e Vec, só podem ser derive(Clone), mas não podem ser derive(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 + Trait2 e where T: Trait1 + Trait2 nas 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áusula where oferece 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) e Sync (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


📝 Exercícios

  1. Dificuldade ⭐: Defina um trait Drawable { fn draw(&self); }. Implemente essa característica nas duas estruturas Circle e Square, de modo que cada uma imprima uma forma diferente. Escreva uma função genérica fn render<T: Drawable>(item: &T) que chame draw. Na função principal, renderize um círculo e um quadrado, respectivamente.

  2. Dificuldade ⭐⭐: Defina uma característica trait Summary { fn summarize(&self) -> String; fn author(&self) -> &str; }, em que author() é o método padrão que retorna "Anonymous". Implemente essa característica para as estruturas Article { title: String, content: String } e Tweet { username: String, text: String }. Crie dois artigos e dois tweets, coloque-os em Vec<Box<dyn Summary>> e percorra-os para imprimir seus resumos.

  3. Dificuldade ⭐⭐⭐: Defina uma hierarquia de traços: trait Vehicle: std::fmt::Debug { fn fuel_type(&self) -> &str; }, seguida de trait ElectricVehicle: Vehicle { fn battery_capacity_kwh(&self) -> f64; fn range_km(&self) -> f64; }. Implemente ElectricVehicle para as estruturas TeslaModel3 e NissanLeaf. Escreva uma função fn 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.

Web-Tutorial.com

Equipe Técnica Web-Tutorial

Uma plataforma de tutoriais mantida por diversos desenvolvedores. Cada tutorial é escrito e revisado por profissionais da área correspondente. Trabalhamos para manter nosso conteúdo preciso e confiável — se encontrar algum problema, avise-nos.

100%