Traits

Default implementation

NewsArticle implements Summary, but defines it's own version. Tweet uses the default implementation by using an empty block.


#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize(&self) -> String {
        String::from("(Read more...)")
    }
}

pub struct NewsArticle {
    pub headline: String,
    pub location: String,
    pub author: String,
    pub content: String,
}

impl Summary for NewsArticle {
    fn summarize(&self) -> String {
        format!("{}, by {} ({})", self.headline, self.author, self.location)
    }
}

pub struct Tweet {
    pub username: String,
    pub content: String,
    pub reply: bool,
    pub retweet: bool,
}

impl Summary for Tweet {}
}

Functions

Function implementing interface

Now we can set function that accepts anything which implements Summary


#![allow(unused)]
fn main() {
pub fn notify(item: &impl Summary) {
    println!("Breaking news! {}", item.summarize());
}
}

Long form

The trait bound function is using short form impl keyword, if we wanted to constrain two arguments to use the same type we'd need to use the long form:


#![allow(unused)]
fn main() {
pub fn notify<T: Summary>(item1: &T, item2: &T) {}
}

Constrain to multiple traits:


#![allow(unused)]
fn main() {
// short form
pub fn notify(item: &(impl Summary + Display)) {}
// long form
pub fn notify<T: Summary + Display>(item: &T) {
}

Where clause

Implementing multiple interfaces across multiple arguments can become long


#![allow(unused)]
fn main() {
fn some_function<T: Display + Clone, U: Clone + Debug>(t: &T, u: &U) -> i32 {
}

There is alternate syntax to make this clearer


#![allow(unused)]
fn main() {
fn some_function<T, U>(t: &T, u: &U) -> i32
    where T: Display + Clone,
          U: Clone + Debug
{}
}

Return trait


#![allow(unused)]
fn main() {
fn returns_summarizable() -> impl Summary {
    Tweet {
        username: String::from("horse_ebooks"),
        content: String::from(
            "of course, as you probably already know, people",
        ),
        reply: false,
        retweet: false,
    }
}
}

Default implementation calling

There is no problem for a default implementation to call a function that hasn't been implemented. This allows the user of a trait to only have to define a small amount of logic.


#![allow(unused)]
fn main() {
pub trait Summary {
    fn summarize_author(&self) -> String;

    fn summarize(&self) -> String {
        format!("(Read more from {}...)", self.summarize_author())
    }
}

impl Summary for Tweet {
    fn summarize_author(&self) -> String {
        format!("@{}", self.username)
    }
}
}

Conditionally implement methods

You can implement a method, only when the type has implemented a trait, the below is an example of a implementing cmp_display() when a the type being used inside Pair has implemented Display and PartialOrd


#![allow(unused)]
fn main() {
use std::fmt::Display;

struct Pair<T> {
    x: T,
    y: T,
}

impl<T> Pair<T> {
    fn new(x: T, y: T) -> Self {
        Self { x, y }
    }
}

impl<T: Display + PartialOrd> Pair<T> {
    fn cmp_display(&self) {
        if self.x >= self.y {
            println!("The largest member is x = {}", self.x);
        } else {
            println!("The largest member is y = {}", self.y);
        }
    }
}
}

Advanced

Placeholder Types (associated type)


#![allow(unused)]
fn main() {
pub trait Iterator {
    type Item;

    fn next(&mut self) -> Option<Self::Item>;
}
}

This can be used like:


#![allow(unused)]

fn main() {
impl Iterator for Counter {
    type Item = u32;

    fn next(&mut self) -> Option<Self::Item> {
        // --snip--
}

Where a u32 is being returned

Operator Overloading

This example is similar in that there is an Output placeholder type that determines what is returned.

use std::ops::Add;

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    assert_eq!(
        Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
        Point { x: 3, y: 3 }
    );
}

This is what the Add trait looks like:


#![allow(unused)]
fn main() {
trait Add<Rhs=Self> {
    type Output;

    fn add(self, rhs: Rhs) -> Self::Output;
}
}

Notice the Add<Rhs=Self>, this is called a Default Generic Type Parameter, below we will override the default of Self to add two different types:

use std::ops::Add;

#[derive(Debug)]
struct Millimeters(u32);
struct Meters(u32);

impl Add<Meters> for Millimeters {
    type Output = Millimeters;

    fn add(self, other: Meters) -> Millimeters {
        Millimeters(self.0 + (other.0 * 1000))
    }
}

#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
    x: i32,
    y: i32,
}

impl Add for Point {
    type Output = Point;

    fn add(self, other: Point) -> Point {
        Point {
            x: self.x + other.x,
            y: self.y + other.y,
        }
    }
}

fn main() {
    println!("{:?}", Point { x: 1, y: 25 } + Point { x: 5, y: 10 });
    let x = Millimeters(2550);
    let y = Meters(3);
    println!("{:?}", x + y);
}

Fully Qualified Disambiguation

When there are multiple implementations of traits with the same method name, you need to fully qualify so Rust knows which one to use:

trait Pilot {
    fn fly(&self);
}

trait Wizard {
    fn fly(&self);
}

struct Human;

impl Pilot for Human {
    fn fly(&self) {
        println!("This is your captain speaking.");
    }
}

impl Wizard for Human {
    fn fly(&self) {
        println!("Up!");
    }
}

impl Human {
    fn fly(&self) {
        println!("*waving arms furiously*");
    }
}

fn main() {
    let person = Human;
    person.fly();
    Wizard::fly(&person);
    Pilot::fly(&person);
}

Note that this only works because &self is passed in, if it doesn't have &self i.e. there's no object that it's attached to, you have to use a different syntax:

trait Animal {
    fn baby_name() -> String;
}

struct Dog;

impl Dog {
    fn baby_name() -> String {
        String::from("Spot")
    }
}

impl Animal for Dog {
    fn baby_name() -> String {
        String::from("puppy")
    }
}

fn main() {
    println!("A baby dog is called a {}", <Dog as Animal>::baby_name());
}

<Dog as Animal>::baby_name()) is refereed to as fully qualified syntax

The spec is:


#![allow(unused)]
fn main() {
<Type as Trait>::function(receiver_if_method, next_arg, ...);
}

Supertraits

Below we use self.to_string(), that method only appears as part of fmt::Display, which is added as a supertrait with OutlinePrint: fmt::Display. Now before OutlinePrint can be implemented on Point, we also need to implement fmt::Display

use std::fmt;

trait OutlinePrint: fmt::Display {
    fn outline_print(&self) {
        let output = self.to_string();
        let len = output.len();
        println!("{}", "*".repeat(len + 4));
        println!("*{}*", " ".repeat(len + 2));
        println!("* {} *", output);
        println!("*{}*", " ".repeat(len + 2));
        println!("{}", "*".repeat(len + 4));
    }
}

struct Point {
    x: i32,
    y: i32,
}

impl fmt::Display for Point {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "({}, {})", self.x, self.y)
    }
}

impl OutlinePrint for Point {}

fn main() {
    let x = Point { x: 25, y: 10 };
    x.outline_print();
}

Newtype Pattern with Traits

Use external traits with external types by wrapping it in a new type like so:

use std::fmt;

struct Wrapper(Vec<String>);

impl fmt::Display for Wrapper {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "[\n  {}\n]", self.0.join("\n  "))
    }
}

fn main() {
    let w = Wrapper(vec![
        String::from("hello"),
        String::from("world"),
        String::from("coolio"),
        String::from("coolio"),
    ]);
    println!("w = {}", w);
}

This allows us to override Display on the Vec type, if you then want to get all the methods on Vec<String> just override the deref:


#![allow(unused)]
fn main() {
impl Deref for Wrapper {
    type Target = Vec<String>;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}
}

Default Trait

This allows you to to set default's that offers some extra functionality:


#![allow(unused)]
fn main() {
#[derive(Debug)]
struct Person {
    name: String,
    age: i32,
}
// We implement the Default trait to use it as a fallback
// when the provided string is not convertible into a Person object
impl Default for Person {
    fn default() -> Person {
        Person {
            name: String::from("John"),
            age: 30,
        }
    }
}
}

You can then fill in all the rest of the options with defaults:


#![allow(unused)]
fn main() {
let john = Person {
	age: 21,
	..Default::default()
};
println!("{john:?}")
}
Person { name: "John", age: 21 }

AsRef

Allows functions to take generic types that implement certain traits

e.g. str and String both implement AsRef<str>, so they can both be used this function where we know that they'll have access to .as_bytes()


#![allow(unused)]
fn main() {
fn byte_counter<T: AsRef<str>>(arg: T) -> usize {
    arg.as_ref().as_bytes().len()
}
}