Rust traits: Difference between revisions

From wikinotes
No edit summary
 
(11 intermediate revisions by the same user not shown)
Line 1: Line 1:
Traits are similar to interfaces except they can have a default implementation.
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 =
= Basics =
<blockquote>
<blockquote>
{{ NOTE |
If the trait method implementation is in a different file than your type,<br>
you'll need to import the trait into the current namespace so it is usable on your object!<br>
ex. <code>std::io::BufRead</code>
}}
Define and implement trait
<syntaxhighlight lang="rust">
<syntaxhighlight lang="rust">
struct Cat { name: String }
struct Cat { name: String }
Line 9: Line 18:
// all implementors of 'Pet' must have method 'play' with this method signature
// all implementors of 'Pet' must have method 'play' with this method signature
trait Pet {
trait Pet {
     fn play(&self);
     fn play(&self) -> bool;
}
}




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


Use trait implementors in method signature
<syntaxhighlight lang="rust">
<syntaxhighlight lang="rust">
// use trait as param type
// use trait as param type
fn play_with_pet(pet: &impl Pet) {
fn play_with_pet(pet: &impl Pet) -> bool {
     pet.play();
     pet.play();
}
}


fn main(){
let cat = Cat{name: "maize".to_string()};
    let cat = Cat{name: "maize".to_string()};
let dog = Dog{name: "midnight".to_string(), breed: "?".to_string()};
    let dog = Dog{name: "midnight".to_string(), breed: "?".to_string()};
play_with_pet(&cat);
    play_with_pet(&cat);
play_with_pet(&dog);
    play_with_pet(&dog);
</syntaxhighlight>
</blockquote><!-- Basics -->
 
= In Function Signatures =
<blockquote>
== As Param ==
<blockquote>
<syntaxhighlight lang="rust">
// with impl
fn play_with_pet(pet: &impl Pet) -> bool {}
 
// trait-bound-syntax (generic)
fn play_with_pet<P: Pet>(pet: P) -> bool {}
</syntaxhighlight>
 
Requires object implements multiple interfaces
<syntaxhighlight lang="rust">
// 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
{}
</syntaxhighlight>
</blockquote><!-- Params -->
 
== As Return ==
<blockquote>
If you're only ever returning one possible concretion, you can do this:
<syntaxhighlight lang="rust">
fn gimme_pet() -> impl Pet {
    // ...
}
}
</syntaxhighlight>
</syntaxhighlight>
</blockquote><!-- Basics -->
 
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
<syntaxhighlight lang="rust">
// TODO
</syntaxhighlight>
</blockquote><!-- As Return -->
</blockquote><!-- Method Signatures -->
 
= Default Method Implementation =
<blockquote>
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)
}}
 
<syntaxhighlight lang="rust">
struct Cat { name: String }
struct Dog { name: String, breed: String }
 
trait Pet {
    fn play(&self) {
        println!("rub")
    }
}
</syntaxhighlight>
</blockquote><!-- Default Method -->
 
= Blanket Implementations =
<blockquote>
You can write traits that are automatically applied all objects,<br>
that implements another trait.<br>
 
<syntaxhighlight lang="rust">
// 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!("{}", )
    }
}
</syntaxhighlight>
</blockquote><!-- Conditional Implementation -->
 
= Builtin Traits =
<blockquote>
There are several builtin traits.<br>
Some must be imported into scope for methods to be available (like refinements),<br>
Others are applied automatically to objects.<br>
These builtin traits are documented (non-exhaustively) in other sections.
 
{| class="wikitable"
|-
| [[rust pointers]]
|-
| [[rust threading]]
|-
|}
</blockquote><!-- Builtin Traits -->

Latest revision as of 04:22, 11 February 2023

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!("{}", )
    }
}

Builtin Traits

There are several builtin traits.
Some must be imported into scope for methods to be available (like refinements),
Others are applied automatically to objects.
These builtin traits are documented (non-exhaustively) in other sections.

rust pointers
rust threading