Définir et implémenter des traits
Maîtrisez les traits pour définir un comportement partagé entre différents types, à l’image des interfaces dans d’autres langages.
Définir et implémenter des traits est une leçon Learn Rust Coding gratuite sur CoddyKit. Ceci est la leçon 2 sur 3. Tu peux lire la leçon complète ci-dessous gratuitement — puis la pratiquer en direct dans le navigateur avec un éditeur de code intégré et un tuteur IA 24/7. Elle fait partie du parcours d'apprentissage Learn Rust Coding, et ta progression se synchronise sur le web et l'application CoddyKit. Le cours Learn Rust Coding comprend 3 leçons au total.
Que sont les traits en Rust ?
En Rust, les traits sont un moyen puissant de définir un comportement partagé entre différents types. Voyez-les comme des interfaces dans d'autres langages.
Un trait indique à Rust qu'un type particulier possède certaines fonctionnalités. Si un type implémente un trait, cela signifie qu'il fournit les méthodes définies par ce trait.
- Les traits permettent le polymorphisme : travailler avec différents types de manière uniforme.
- Ils sont essentiels au système de types de Rust et aux abstractions sûres.
Déclarer votre premier trait
Vous définissez un trait à l'aide du mot-clé trait. À l'intérieur, vous indiquez les signatures de méthodes (noms, paramètres et types de retour) que tout type qui l'implémente doit fournir.
Définissons un trait Summary simple pour les éléments qui peuvent être résumés.
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.");
}Implémenter des traits pour des types
Pour qu'un type utilise un trait, vous employez la syntaxe impl Trait for Type. Vous fournissez ensuite l'implémentation concrète de chaque méthode définie dans le trait.
Ici, nous implémentons Summary pour une structure 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());
}Utiliser des fonctions avec contrainte de trait
Une fois qu'un type implémente un trait, vous pouvez écrire des fonctions qui acceptent n'importe quel type implémentant ce trait. Cela permet d'écrire du code flexible et générique.
La fonction notify peut accepter n'importe quel type qui implémente 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);
}Traits avec comportement par défaut
Les traits peuvent également fournir des implémentations par défaut pour leurs méthodes. Ainsi, les types ne sont pas obligés d'implémenter chaque méthode lorsqu'une implémentation par défaut est fournie.
Ils peuvent choisir d'utiliser la valeur par défaut ou de la remplacer par leur propre logique.
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());
}Fonctions génériques avec des traits
Lorsque vous écrivez des fonctions ou des structures génériques, vous pouvez utiliser des contraintes de traits pour préciser qu'un paramètre de type générique T doit implémenter un trait donné.
Cela garantit que les méthodes de ce trait sont disponibles pour 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
}Combiner plusieurs contraintes de traits
Parfois, un type générique doit implémenter plusieurs traits. Vous pouvez préciser plusieurs contraintes de traits à l'aide de l'opérateur +.
Pour les situations plus complexes, notamment avec de nombreux paramètres génériques, la clause where peut améliorer la lisibilité.
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);
}Retourner des types avec `impl Trait`
La syntaxe impl Trait ne sert pas uniquement aux paramètres de fonction ; elle peut également être utilisée pour les valeurs de retour. C'est utile lorsque vous voulez retourner un type qui implémente un trait donné, sans exposer son type concret exact.
Elle simplifie les signatures de fonctions et préserve la flexibilité de votre API.
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());
}S'appuyer sur les supertraits
Parfois, un trait peut logiquement dépendre d'un autre. Rust vous permet de définir des supertraits, ce qui signifie qu'un trait peut exiger que tout type qui l'implémente implémente également un autre trait précis.
Cela se fait avec la syntaxe trait SubTrait: SuperTrait. C'est comme dire : « si vous savez faire X, vous devez également savoir faire 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();
}Testez vos connaissances sur les traits
Considérez l'extrait de code Rust suivant. Laquelle des affirmations suivantes concernant les traits et les contraintes de traits est VRAIE ?
Les traits : votre boîte à outils comportementale
Nous avons exploré le puissant système de traits de Rust !
- Les traits définissent un ensemble de méthodes qu'un type peut implémenter, à la manière des interfaces.
- Vous implémentez des traits pour des types précis à l'aide de
impl Trait for Type. - Les contraintes de traits (
T: Trait) permettent aux fonctions et aux structures génériques de fonctionner avec tout type qui implémente le trait requis. - Les traits peuvent avoir des implémentations par défaut, et vous pouvez préciser plusieurs contraintes de traits (
T: Trait1 + Trait2) ou utiliser des supertraits. impl Traitpeut également être utilisé pour les valeurs de retour afin de masquer les types concrets.
Les traits sont fondamentaux pour écrire du code Rust flexible, réutilisable et sûr !
Questions Fréquemment Posées
La leçon « Définir et implémenter des traits » est-elle gratuite ?
Oui — le texte complet de « Définir et implémenter des traits » est gratuit à lire ici sur le web. Pour la pratiquer de manière interactive (un éditeur de code intégré et un tuteur IA 24/7) et déverrouiller le reste du cours Learn Rust Coding, passe à CoddyKit PRO. Le cours Learn Rust Coding comprend 3 leçons au total.
Qu'est-ce que j'apprendrai dans « Définir et implémenter des traits » ?
Maîtrisez les traits pour définir un comportement partagé entre différents types, à l’image des interfaces dans d’autres langages. Tu pratiques Learn Rust Coding avec du code pratique que tu exécutes directement dans le navigateur, et un tuteur IA 24/7 répond à tes questions au fur et à mesure que tu avances dans la leçon.
Dois-je avoir de l'expérience pour commencer Learn Rust Coding ?
Aucune expérience préalable n'est requise. Learn Rust Coding sur CoddyKit est structuré pour les débutants jusqu'aux apprenants avancés, donc tu peux commencer ici ou depuis le début et avancer à ton rythme. Ceci est la leçon 2 sur 3.
Combien de temps prend la leçon « Définir et implémenter des traits » ?
La plupart des leçons CoddyKit prennent environ 5–10 minutes. Chacune est courte et interactive, tu progresses régulièrement et tu repiques exactement où tu t'es arrêté sur le web et l'app.
Peux-tu écrire et exécuter du code dans cette leçon Learn Rust Coding ?
Oui. Chaque leçon Learn Rust Coding inclut un éditeur de code intégré, tu écris et exécutes du vrai code directement dans ton navigateur et tu reçois des retours IA instantanés — aucune configuration locale requise.
Toutes les leçons de ce cours
- Écrire du code générique en Rust
- Définir et implémenter des traits
- Utilisation avancée des traits : types associés