Traits from the Ground Up

Pascal Hertleif

2018-09-05

Hi, I’m Pascal Hertleif

Warning: Lots of code

Interrupt me when you have a question

(Please use the mic)

Data and Behavior

Let’s do stuff

fn make_true(input: &str) -> String {
  format!("{}!!", input)
}

Wrap data

struct Fact { text: String }

fn make_true(input: &Fact) -> Fact {
  Fact { text: format!("{}!!", input.text) }
}

Add Behavior to Data

How to write a method

  1. Write: impl YourType {
  2. Write: some function
  3. Write: }

make_true as a method

struct Fact { text: String }

impl Fact {
  fn make_true(&self) -> Fact {
    Fact { text: format!("{}!!", self.text) }
  }
}

Using the method

let fact = Fact { text: String::from("Lorem ipsum") };
let true_fact = fact.make_true();

self?

Methods can operate on the data

Access the data using a form of self as first parameter

“A form of self”?

You need to specify how you want self:

  • &self: Borrowed, read-only version
  • &mut self: Mutable, borrowed version
  • [mut] self: Owned version, do whatever you want with it (incl. destroying it)

Another make_true

struct Fact { text: String }

impl Fact {
  fn make_true(&mut self) {
    self.text.push_str("!!");
  }
}

Or:

struct Fact { text: String }

impl Fact {
  fn make_true(mut self) -> Fact {
    self.text.push_str("!!");
    self
  }
}

Is this a trait?

Let’s give it a name

trait Truth {
  fn make_true(&self) -> Self
}

Now we can implement it

impl Truth for Fact {
  fn make_true(&self) -> Self {
    Fact { text: format!("{}!!", self.text) }
  }
}

Implement it for everything!

impl Truth for i32 {
  fn make_true(&self) -> Self {
    42
  }
}

Abstract over behavior

T: Truth

fn print_news<T: Truth>(facts: &[T]) {
  for fact in facts {
    let fact = fact.make_true();
    println!("{}", fact);
  }
}

Wait a second:

error[E0277]: `T` doesn't implement `std::fmt::Display`
  --> src/lib.rs:16:20
   |
16 |     println!("{}", fact.make_true());
   |                    ^^^^^^^^^^^^^^^^ `T` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `T`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = help: consider adding a `where T: std::fmt::Display` bound
   = note: required by `std::fmt::Display::fmt`

What do we know about T?

  • It implements Truth
  • ???
  • Nope, that’s it*
use std::fmt;

fn print_news<T: Truth + fmt::Display>(facts: &[T]) {
  for fact in facts {
    let fact = fact.make_true();
    println!("{}", fact);
  }
}

But we’ll also need:

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

Here we go:

fn main() {
    let facts = vec![
      Fact { text: String::from("lorem ipsum") },
    ];

    print_news(&facts);
}
$ cargo run
lorem ipsum!!

where syntax

Instead of

fn print_news<T: Truth + fmt::Display>(facts: &[T]) {}

we can also write

fn print_news<T>(facts: &[T]) where T: Truth + fmt::Display {}

Using Generics in impls

impl<T: Debug> Foo for Vec<T> {
  // ...
}

Associated items

Associated functions

That’s what we’ve been writing all this time

Associated types

specify types you need to refer to that are specific to each impl block

trait Iterator {
  type Item;
  fn next(&mut self) -> Option<Self::Item>;
}
impl Iterator for FourIntegers {
  type Item = i32;
  fn next(&mut self) -> Option<i32> {}
}

Associated constants

trait SuperLog {
  const LABEL: Display;

  fn log(&self, f: &mut fmt::Formatter) -> fmt::Result;
}

Good to know

Unambiguous function call syntax

fact.make_true()

expands to

<Fact as Truth>::make_true(fact)

Operators are traits

a + b expands to std::ops::Add::add(a, b)

because of:

impl Add<i32> for i32 {
  type Output = i32;
  // ...
}

Marker traits

trait Fancy {}

impl Fancy for Vec<String> {}

Thanks!

Any questions?

I am Pascal

{twitter,github}.com/killercup

Slides: git.io/traits-from-the-ground-up