Definição e Implementação de Traits
Domine as traits para definir comportamentos partilhados entre diferentes tipos, de forma semelhante às interfaces noutras linguagens.
Definição e Implementação de Traits é uma aula grátis de Learn Rust Coding no CoddyKit. Esta é a aula 2 de 3. Você pode ler a aula completa abaixo gratuitamente — depois pratica ao vivo no navegador com um editor de código integrado e um tutor de IA 24/7. Faz parte do caminho de aprendizado de Learn Rust Coding, e seu progresso é sincronizado entre a web e o app CoddyKit. O curso de Learn Rust Coding inclui 3 aulas no total.
O que são características no Rust?
No Rust, as características são uma forma poderosa de definir comportamentos compartilhados entre diferentes tipos. Pense nelas como interfaces de outras linguagens.
Uma característica informa ao Rust que determinado tipo tem certa funcionalidade. Se um tipo implementa uma característica, isso significa que ele fornece os métodos definidos por essa característica.
- As características permitem polimorfismo: trabalhar com diferentes tipos de maneira uniforme.
- Elas são essenciais para o sistema de tipos do Rust e para abstrações seguras.
Declarando sua primeira característica
Você define uma característica usando a palavra-chave trait. Dentro dela, liste as assinaturas dos métodos (nomes, parâmetros e tipos de retorno) que qualquer tipo que a implemente deverá fornecer.
Vamos definir uma característica simples Summary para itens que podem ser resumidos.
trait Summary {
fn summarize(&self) -> String;
}
fn main() {
// Traits are definitions, not directly executable.
// We will implement and use them in later scenes!
println!("Trait 'Summary' defined.");
}Implementando características para tipos
Para fazer um tipo usar uma característica, use a sintaxe impl Trait for Type. Em seguida, forneça a implementação concreta de cada método definido na característica.
Aqui, implementamos Summary para uma estrutura NewsArticle.
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
fn main() {
let article = NewsArticle {
headline: String::from("Penguins win Stanley Cup!"),
location: String::from("Pittsburgh, PA"),
author: String::from("Iceburgh"),
content: String::from("The Pittsburgh Penguins have won..."),
};
println!("New article summary: {}", article.summarize());
}Usando funções com limites de características
Depois que um tipo implementa uma característica, você pode escrever funções que aceitem qualquer tipo que implemente essa característica. Isso permite criar código flexível e genérico.
A função notify pode receber qualquer tipo que implemente Summary.
trait Summary {
fn summarize(&self) -> String;
}
struct NewsArticle {
headline: String,
location: String,
author: String,
content: String,
}
impl Summary for NewsArticle {
fn summarize(&self) -> String {
format!("{}, by {} ({})", self.headline, self.author, self.location)
}
}
struct Tweet {
username: String,
content: String,
reply: bool,
retweet: bool,
}
impl Summary for Tweet {
fn summarize(&self) -> String {
format!("{}: {}", self.username, self.content)
}
}
fn notify(item: &impl Summary) { // impl Summary is syntax sugar for a trait bound
println!("Breaking news! {}", item.summarize());
}
fn main() {
let tweet = Tweet {
username: String::from("horse_ebooks"),
content: String::from("of course, as you probably already know, people"),
reply: false,
retweet: false,
};
let article = NewsArticle {
headline: String::from("Rust is awesome!"),
location: String::from("Internet"),
author: String::from("Rustacean"),
content: String::from("Rust's type system is amazing."),
};
notify(&tweet);
notify(&article);
}Características com comportamento padrão
As características também podem fornecer implementações padrão para seus métodos. Isso significa que os tipos não precisam implementar todos os métodos quando existe uma implementação padrão.
Eles podem optar por usar o padrão ou substituí-lo por sua própria lógica.
trait Summary {
fn summarize_author(&self) -> String; // New required method
fn summarize(&self) -> String { // Default implementation
format!("(Read more from {})", self.summarize_author())
}
}
struct Tweet {
username: String,
content: String,
reply: bool,
retweet: bool,
}
impl Summary for Tweet {
fn summarize_author(&self) -> String { // Must implement required method
format!("@{}", self.username)
}
// We are using the default summarize() method here!
}
fn main() {
let tweet = Tweet {
username: String::from("dog_lover"),
content: String::from("My dog is the best!"),
reply: false,
retweet: false,
};
println!("Tweet summary: {}", tweet.summarize());
}Funções genéricas com características
Ao escrever funções ou estruturas genéricas, você pode usar limites de características para especificar que um parâmetro de tipo genérico T deve implementar determinada característica.
Isso garante que os métodos dessa característica estejam disponíveis para T.
trait Displayable {
fn display(&self);
}
struct Point<T> {
x: T,
y: T,
}
// Implement Displayable for Point<i32>
impl Displayable for Point<i32> {
fn display(&self) {
println!("Point: ({}, {})", self.x, self.y);
}
}
// Generic function that works for any type T that implements Displayable
fn print_item<T: Displayable>(item: T) {
item.display();
}
fn main() {
let p = Point { x: 10, y: 20 };
print_item(p);
// This function only works for types that implement Displayable
// let s = String::from("Hello");
// print_item(s); // This would cause a compile-time error
}Combinando vários limites de características
Às vezes, um tipo genérico precisa implementar mais de uma característica. Você pode especificar vários limites de características usando o operador +.
Em cenários mais complexos, especialmente com muitos parâmetros genéricos, a cláusula where pode melhorar a legibilidade.
use std::fmt::Debug;
trait Printable {
fn print_info(&self);
}
struct Book {
title: String,
pages: u32,
}
impl Printable for Book {
fn print_info(&self) {
println!("Book: '{}' ({} pages)", self.title, self.pages);
}
}
impl Debug for Book { // Book also implements Debug trait
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Book {{ title: {}, pages: {} }}", self.title, self.pages)
}
}
// A function that requires its argument to be both Printable and Debug
fn process_item<T: Printable + Debug>(item: T) {
item.print_info();
println!("Debug info: {:?}", item);
}
fn main() {
let my_book = Book {
title: String::from("The Rust Book"),
pages: 600,
};
process_item(my_book);
}Retornando tipos com `impl Trait`
A sintaxe impl Trait não serve apenas para parâmetros de função; ela também pode ser usada em posições de retorno. Isso é útil quando você quer retornar um tipo que implementa determinada característica, mas não quer expor seu tipo concreto exato.
Ela simplifica as assinaturas de funções e mantém sua API flexível.
trait Greeter {
fn greet(&self) -> String;
}
struct FriendlyGreeter;
impl Greeter for FriendlyGreeter {
fn greet(&self) -> String {
String::from("Hello there!")
}
}
struct FormalGreeter;
impl Greeter for FormalGreeter {
fn greet(&self) -> String {
String::from("Greetings and salutations.")
}
}
// This function returns *some* type that implements Greeter
// The caller doesn't need to know if it's FriendlyGreeter or FormalGreeter
fn get_greeter(formal: bool) -> impl Greeter {
if formal {
FormalGreeter
} else {
FriendlyGreeter
}
}
fn main() {
let casual_greet = get_greeter(false);
println!("{}", casual_greet.greet());
let formal_greet = get_greeter(true);
println!("{}", formal_greet.greet());
}Desenvolvendo a partir de supercaracterísticas
Às vezes, uma característica pode depender logicamente de outra. O Rust permite definir supercaracterísticas, o que significa que uma característica pode exigir que qualquer tipo que a implemente também implemente outra característica específica.
Isso é feito com a sintaxe trait SubTrait: SuperTrait. É como dizer: “se você consegue fazer X, também deve conseguir fazer Y”.
trait Printable {
fn print_content(&self);
}
// Debug is a supertrait of PrintableDebug
// Any type implementing PrintableDebug must also implement Debug
trait PrintableDebug: Printable + std::fmt::Debug {
fn print_debug_and_content(&self) {
self.print_content();
println!("Debug info: {:?}", self);
}
}
struct Item {
id: u32,
name: String,
}
impl Printable for Item {
fn print_content(&self) {
println!("Item ID: {}, Name: {}", self.id, self.name);
}
}
impl std::fmt::Debug for Item {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Item {{ id: {}, name: {} }}", self.id, self.name)
}
}
// Now we can implement PrintableDebug because Item implements both Printable and Debug
impl PrintableDebug for Item {}
fn main() {
let my_item = Item {
id: 101,
name: String::from("Widget A"),
};
my_item.print_debug_and_content();
}Teste seus conhecimentos sobre características
Considere o seguinte trecho de código Rust. Qual das seguintes afirmações sobre características e limites de características é VERDADEIRA?
Características: seu conjunto de ferramentas para comportamentos
Exploramos o poderoso sistema de características do Rust!
- As características definem um conjunto de métodos que um tipo pode implementar, funcionando como interfaces.
- Você implementa características para tipos específicos usando
impl Trait for Type. - Os limites de características (
T: Trait) permitem que funções e estruturas genéricas operem com qualquer tipo que implemente a característica exigida. - As características podem ter implementações padrão, e você pode especificar vários limites de características (
T: Trait1 + Trait2) ou usar supercaracterísticas. impl Traittambém pode ser usado em posições de retorno para ocultar tipos concretos.
As características são fundamentais para escrever código Rust flexível, reutilizável e seguro!
Perguntas Frequentes
A aula “Definição e Implementação de Traits” é grátis?
Sim — o texto completo de “Definição e Implementação de Traits” é grátis para ler aqui na web. Para praticá-la interativamente (um editor de código integrado e um tutor de IA 24/7) e desbloquear o restante do curso de Learn Rust Coding, atualize para CoddyKit PRO. O curso de Learn Rust Coding inclui 3 aulas no total.
O que vou aprender em “Definição e Implementação de Traits”?
Domine as traits para definir comportamentos partilhados entre diferentes tipos, de forma semelhante às interfaces noutras linguagens. Você pratica Learn Rust Coding com código prático que executa diretamente no navegador, e um tutor de IA 24/7 responde suas dúvidas enquanto trabalha na aula.
Preciso ter experiência prévia para começar Learn Rust Coding?
Nenhuma experiência prévia é necessária. Learn Rust Coding no CoddyKit é estruturado para alunos iniciantes até avançados, então você pode começar aqui ou desde o início e aprender no seu ritmo. Esta é a aula 2 de 3.
Quanto tempo leva a aula “Definição e Implementação de Traits”?
A maioria das aulas CoddyKit leva cerca de 5–10 minutos. Cada uma é compacta e interativa, então você faz progresso constante e retoma exatamente de onde parou entre web e app.
Posso escrever e executar código nesta aula de Learn Rust Coding?
Sim. Cada aula de Learn Rust Coding inclui um editor de código integrado, então você escreve e executa código real direto no navegador e recebe feedback de IA instantaneamente — nenhuma configuração local necessária.
Todas as aulas deste curso
- Escrita de Código Genérico em Rust
- Definição e Implementação de Traits
- Utilização Avançada de Traits: Tipos Associados