Leer programmeren met Rust · Les

Traits definiëren en implementeren

Beheers traits voor het definiëren van gedeeld gedrag tussen verschillende typen, vergelijkbaar met interfaces in andere talen.

Les 2 van 311 stappen

Traits definiëren en implementeren is een gratis Leer programmeren met Rust-les op CoddyKit. Dit is les 2 van 3. Je kunt 3 lessen uit dit leerpad gratis volledig lezen — daarna ontgrendelt CoddyKit PRO alle lessen, plus praktische oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. Deze les maakt deel uit van het leertraject Leer programmeren met Rust. Je voortgang wordt gesynchroniseerd op het web en in de CoddyKit-app. De cursus Leer programmeren met Rust bevat in totaal 3 lessen.

Wat zijn traits in Rust?

In Rust zijn traits een krachtige manier om gedeeld gedrag voor verschillende typen te definiëren. Je kunt ze vergelijken met interfaces in andere talen.

Een trait vertelt Rust dat een bepaald type bepaalde functionaliteit heeft. Als een type een trait implementeert, betekent dit dat het type de door die trait gedefinieerde methoden aanbiedt.

  • Traits maken polymorfisme mogelijk: werken met verschillende typen op een uniforme manier.
  • Ze zijn essentieel voor het typesysteem en veilige abstracties van Rust.

Je eerste trait declareren

Je definieert een trait met het sleutelwoord trait. Daarin vermeld je methodesignaturen (namen, parameters en retourtypen) die elk type dat de trait implementeert moet aanbieden.

We definiëren een eenvoudige trait Summary voor items die kunnen worden samengevat.

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.");
}

Traits voor typen implementeren

Om een type een trait te laten gebruiken, gebruik je de syntaxis impl Trait for Type. Vervolgens geef je de concrete implementatie voor elke methode die in de trait is gedefinieerd.

Hier implementeren we Summary voor een struct 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());
}

Functies met trait-beperkingen gebruiken

Zodra een type een trait implementeert, kun je functies schrijven die elk type accepteren dat die trait implementeert. Hierdoor kun je flexibele en generieke code schrijven.

De functie notify kan elk type accepteren dat Summary implementeert.

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 met standaardgedrag

Traits kunnen ook standaardimplementaties voor hun methoden bieden. Dit betekent dat typen niet elke methode hoeven te implementeren als er een standaardimplementatie beschikbaar is.

Ze kunnen ervoor kiezen de standaardimplementatie te gebruiken of deze te overschrijven met hun eigen logica.

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());
}

Generieke functies met traits

Bij het schrijven van generieke functies of structs kun je trait-beperkingen gebruiken om vast te leggen dat een generieke typeparameter T een bepaalde trait moet implementeren.

Hierdoor weet je zeker dat de methoden van die trait beschikbaar zijn voor 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
}

Meerdere trait-beperkingen combineren

Soms moet een generiek type meer dan één trait implementeren. Je kunt meerdere trait-beperkingen opgeven met de operator +.

Voor complexere situaties, vooral met veel generieke parameters, kan de where-clausule de leesbaarheid verbeteren.

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);
}

Typen teruggeven met `impl Trait`

De syntaxis impl Trait is niet alleen bedoeld voor functieparameters; je kunt deze ook gebruiken op retourposities. Dit is handig wanneer je een type wilt retourneren dat een bepaalde trait implementeert, maar het exacte concrete type niet openbaar wilt maken.

Zo worden functiesignaturen eenvoudiger en blijft je API flexibel.

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());
}

Voortbouwen op supertraits

Soms is de ene trait logisch afhankelijk van een andere. Met Rust kun je supertraits definiëren. Dat betekent dat een trait kan vereisen dat elk type dat deze implementeert, ook een andere specifieke trait implementeert.

Dit doe je met de syntaxis trait SubTrait: SuperTrait. Het komt erop neer: 'als je X kunt doen, moet je ook Y kunnen doen'.

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();
}

Test je kennis van traits

Bekijk het volgende codefragment in Rust. Welke van de volgende uitspraken over traits en traitgrenzen is WAAR?

Traits: je gereedschapskist voor gedrag

We hebben het krachtige trait-systeem van Rust verkend!

  • Traits definiëren een verzameling methoden die een type kan implementeren en werken daarmee als interfaces.
  • Je implementeert traits voor specifieke typen met impl Trait for Type.
  • Traitgrenzen (T: Trait) zorgen ervoor dat generieke functies en structs kunnen werken met elk type dat een vereiste trait implementeert.
  • Traits kunnen standaardimplementaties hebben. Je kunt ook meerdere traitgrenzen (T: Trait1 + Trait2) opgeven of supertraits gebruiken.
  • Je kunt impl Trait ook op retourposities gebruiken om concrete typen te verbergen.

Traits zijn essentieel voor het schrijven van flexibele, herbruikbare en veilige Rust-code!

Gratis beginnen

Leer Rust met een AI-tutor — gratis

Schrijf echte code en voer die uit in je browser, krijg direct hulp van een AI-tutor die 24/7 beschikbaar is en ga verder waar je gebleven bent op het web of in de app.

Cursussen
39
Lessen
144

Veelgestelde vragen

Is de les “Traits definiëren en implementeren” gratis?

Ja — je kunt hier op het web alle 3 lessen van het leerpad Leer programmeren met Rust, waaronder “Traits definiëren en implementeren”, gratis volledig lezen. Daarna ontgrendelt CoddyKit PRO alle lessen, plus interactieve oefeningen met een ingebouwde code-editor en een AI-tutor die 24/7 beschikbaar is. De cursus Leer programmeren met Rust bevat in totaal 3 lessen.

Wat leer ik in “Traits definiëren en implementeren”?

Beheers traits voor het definiëren van gedeeld gedrag tussen verschillende typen, vergelijkbaar met interfaces in andere talen. Je oefent met Leer programmeren met Rust door code rechtstreeks in de browser uit te voeren. Een AI-begeleider die 24/7 beschikbaar is beantwoordt je vragen terwijl je de les doorwerkt.

Heb ik ervaring nodig om met Leer programmeren met Rust te beginnen?

Ervaring vooraf is niet nodig. Leer programmeren met Rust op CoddyKit is opgebouwd voor beginners tot gevorderden, zodat je hier of bij het begin kunt starten en in je eigen tempo kunt leren. Dit is les 2 van 3.

Hoe lang duurt de les “Traits definiëren en implementeren”?

De meeste lessen van CoddyKit duren ongeveer 5–10 minuten. Elke les is kort en interactief, zodat je gestaag vooruitgaat en op het web en in de app precies verdergaat waar je was gebleven.

Kan ik code schrijven en uitvoeren in deze les over Leer programmeren met Rust?

Ja. Elke les over Leer programmeren met Rust bevat een ingebouwde code-editor, zodat je rechtstreeks in je browser echte code kunt schrijven en uitvoeren en direct feedback van AI krijgt — lokale installatie is niet nodig.

Alle lessen in deze cursus

  1. Generieke code schrijven in Rust
  2. Traits definiëren en implementeren
  3. Geavanceerd gebruik van traits: associated types
← Terug naar Leer programmeren met Rust