Sintaxe de Restrições de Trait
Parte da seção Object Oriented Programming do Journey de Rust da Coddy — lição 40 de 61.
Até agora, você aprendeu a definir traits e implementá-las para structs. Você também trabalhou com genéricos para escrever código flexível. Agora é hora de combinar esses conceitos — usando traits para restringir quais tipos uma função genérica pode aceitar.
Considere uma função genérica que precisa chamar um método específico em seu parâmetro. Sem quaisquer restrições, o Rust não tem garantia de que o tipo T possui esse método:
fn print_info<T>(item: T) {
println!("{}", item.summarize()); // Erro! T pode não ter summarize()
}
A solução é um vínculo de trait (trait bound). Ao adicionar : TraitName após o parâmetro genérico, você diz ao Rust que T deve implementar essa trait:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize()); // Agora o Rust sabe que este método existe
}
A sintaxe <T: Summary> lê-se como "T é qualquer tipo que implementa Summary." Agora a função aceita apenas tipos que possuem o comportamento exigido. Se você tentar passar um tipo que não implementa Summary, o compilador o rejeitará com um erro claro.
Este padrão é poderoso porque permite que você escreva código genérico que ainda tem acesso a funcionalidades específicas. Sua função permanece flexível — ela funciona com qualquer tipo — mas apenas tipos que fornecem o comportamento que você precisa.
Desafio
FácilVamos construir um sistema de notificação que usa trait bounds para garantir que apenas mensagens formatadas corretamente possam ser enviadas! Você criará uma função genérica que aceita qualquer tipo que implemente uma trait específica, demonstrando como as trait bounds restringem os parâmetros genéricos.
Você organizará seu código em dois arquivos:
notification.rs: Defina uma trait públicaNotifycom um método chamadomessageque recebe&selfe retorna umaString. Em seguida, crie duas structs públicas que implementem essa trait:Email— com um campo públicosubject(String). Seu métodomessagedeve retornarEmail: {subject}SMS— com um campo públicocontent(String). Seu métodomessagedeve retornarSMS: {content}
send_notificationque aceita qualquer tipoTque implemente a traitNotify. Esta função deve imprimir o resultado da chamada demessage()no item.main.rs: Importe seu módulo de notificação e use as entradas fornecidas para criar uma instância deEmaile uma deSMS. Chamesend_notificationcom cada uma para demonstrar que sua função genérica funciona com qualquer tipo que implementeNotify.
O ponto principal aqui é que send_notification não conhece o tipo concreto que recebe — ele apenas sabe que o tipo pode produzir uma mensagem. A trait bound T: Notify garante essa capacidade.
Sua saída deve mostrar ambas as notificações sendo enviadas:
Email: {subject}
SMS: {content}Por exemplo, com as entradas Meeting Tomorrow e On my way!:
Email: Meeting Tomorrow
SMS: On my way!Você receberá duas entradas: o assunto do e-mail e o conteúdo do SMS.
Folha de consulta
Um trait bound restringe um parâmetro de tipo genérico para aceitar apenas tipos que implementam uma trait específica.
A sintaxe para trait bounds usa : TraitName após o parâmetro genérico:
fn function_name<T: TraitName>(item: T) {
// Agora pode chamar métodos de TraitName no item
}
Exemplo com um trait bound:
trait Summary {
fn summarize(&self) -> String;
}
fn print_info<T: Summary>(item: T) {
println!("{}", item.summarize());
}
A notação <T: Summary> significa "T é qualquer tipo que implementa Summary." Isso permite que a função chame métodos da trait enquanto permanece genérica.
Experimente você mesmo
mod notification;
use notification::{Email, SMS, send_notification};
fn main() {
// Ler as entradas
let mut subject = String::new();
std::io::stdin().read_line(&mut subject).expect("Failed to read line");
let subject = subject.trim().to_string();
let mut content = String::new();
std::io::stdin().read_line(&mut content).expect("Failed to read line");
let content = content.trim().to_string();
// TODO: Criar uma instância de Email com o assunto
// TODO: Criar uma instância de SMS com o conteúdo
// TODO: Chamar send_notification com o email
// TODO: Chamar send_notification com o sms
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Object Oriented Programming
1Métodos e Comportamento
Introdução a Blocos de ImplementaçãoO Parâmetro SelfMétodos MutáveisFunções AssociadasMúltiplos Blocos de ImplementaçãoEncadeamento de MétodosRecapitulação - Ações de Retângulo4Projeto: Pet Virtual
Definindo o PetAlimentando o Pet7Traits Padrão
A Trait DebugA Trait DisplayClone e CopyTraits de IgualdadeRecapitulação - Ponto Imprimível10Projeto: Sistema de Documentos
A Trait DrawComponente de Texto2Encapsulamento e Módulos
Fundamentos de MódulosA Palavra-chave PublicCampos PrivadosGettersSettersRecapitulação - Cofre Seguro5Genéricos
Structs GenéricasMétodos GenéricosMúltiplos Tipos GenéricosFunções GenéricasRecapitulação - Ponto de Coordenada8Traits como Restrições
Sintaxe de Restrições de TraitRestrições MúltiplasA Cláusula WhereRetornando Tipos com TraitsRecapitulação - Impressora Genérica11Padrões de Projeto em Rust
Padrão NewtypeComposiçãoA Trait DropFrom e IntoRecapitulação - Mock de Smart Pointer3Enums Avançados
Enums com DadosMétodos em EnumsCorrespondência de Variantes de DadosO Enum Option RevisitadoRecapitulação - Enum Shape6Definição de Traits
O que é uma Trait?Implementando TraitsImplementações PadrãoSobrescrevendo PadrõesTraits com ParâmetrosRecapitulação - Media Player