Rust traits

From wikinotes
Revision as of 23:49, 9 February 2023 by Will (talk | contribs)

Traits are similar to interfaces with some key differences:

  • they can have a default implementation.
  • they can behave like refinements, only adding the method if the trait has been imported into namespace

Basics

NOTE:

If the trait method implementation is in a different file than your type,
you'll need to import the trait into the current namespace so it is usable on your object!
ex. std::io::BufRead

Define and implement trait

struct Cat { name: String }
struct Dog { name: String, breed: String }

// all implementors of 'Pet' must have method 'play' with this method signature
trait Pet {
    fn play(&self) -> bool;
}


impl Pet for Cat {
    fn play(&self) -> bool {
        println!("you give a ball of yarn to {}", self.name);
        true
    }
}

Use trait implementors in method signature

// use trait as param type
fn play_with_pet(pet: &impl Pet) -> bool {
    pet.play();
}

let cat = Cat{name: "maize".to_string()};
let dog = Dog{name: "midnight".to_string(), breed: "?".to_string()};
play_with_pet(&cat);
play_with_pet(&dog);

In Function Signatures

As Param

// with impl
fn play_with_pet(pet: &impl Pet) -> bool {}

// trait-bound-syntax (generic)
fn play_with_pet<P: Pet>(pet: P) -> bool {}

Requires object implements multiple interfaces

// with impl
fn play_with_pet(pet: &(impl Pet + Display)) -> bool {}

// with where clause
fn play_with_pet<P>(p: P) -> bool
where
    P: Pet + Display
{}

As Return

If you're only ever returning one possible concretion, you can do this:

fn gimme_pet() -> impl Pet {
    // ...
}

But if you may return different types of implementors of Pet, you'll need to do this: https://doc.rust-lang.org/stable/book/ch17-02-trait-objects.html#using-trait-objects-that-allow-for-values-of-different-types

// TODO

Default Method Implementation

Unlike interfaces in most other languages, you can define a default method implementation on the trait.

TODO:

can you require fields as well? (in this case, name)

struct Cat { name: String }
struct Dog { name: String, breed: String }

trait Pet {
    fn play(&self) {
        println!("rub")
    }
}

Blanket Implementations

You can write traits that are automatically applied all objects,
that implements another trait.

// every object that implements `Display`
// will automagically be assigned this `to_string()` method.

impl<T: Display> ToString for T {
    fn to_string(v: T) -> String {
        format!("{}", )
    }
}