In the previous section, we introduced simple ways to use traits like “interfaces.” In this section, we’ll take a look at some advanced features related to traits

Use association types in the definition of traits to specify placeholder types

Associated types are type placeholders in traits that can be used in method signatures on traits. For example, the Iterator trait contains the associative type Item:

trait Iterator {
  type Item;
  fn next(&mut self) - >Option<Self::Item>;
}
Copy the code

The Iterator trait uses this association type:

struct Counter {
  value: u32
}

impl Iterator for Counter {
  type Item = u32; // Define the Item type
  fn next(&mut self) - >Option<Self::Item> { // Return Option
      <:item>
    if self.value < 3 {
      self.value += 1;
      Some(self.value)
    } else {
      None}}}let mut counter = Counter {
  value: 0
};

println!("{:? }", counter.next()); // Some(1)
println!("{:? }", counter.next()); // Some(2)
println!("{:? }", counter.next()); // Some(3)
println!("{:? }", counter.next()); // None
Copy the code

Some of you might be wondering, why not use generics, for example to implement Iterator using generics:

trait Iterator<T> { // Define generic parameters
  fn next(&mut self) - >Option<T>;
}

// Use the U32 type
impl Iterator<u32> for Counter {
  fn next(&mut self) - >Option<u32> {
    if self.value < 3 {
      self.value += 1;
      Some(self.value)
    } else {
      None}}}println!("{:? }", counter.next()); // Some(1)
println!("{:? }", counter.next()); // Some(2)
println!("{:? }", counter.next()); // Some(3)
println!("{:? }", counter.next()); // None
Copy the code

The Iterator trait could have been implemented above using associative types and generics, but why design an associative type instead of generics?

Imagine using generics to implement iterators that implement u32 types: Impl Iterator

for Counter, impl Iterator

for Counter, next returns a String each time, If we implement both types of iterators for Counter, which type of next method should we use during the for loop? To avoid this conflict, only associative types are used to implement Iterator traits, thereby preventing the behavior of a single type from implementing the same trait more than once.

Operator overloading

You can override the + operator in rust by adding the + operator to the structure.

use std::ops::Add;
#[derive(Debug)]
struct Point {
  x: i32,
  y: i32,}// Overloads the + operator for Point
impl Add for Point {
  // define the return value of the association type Output as add
  type Output = Point;
  fn add(self, other: Point) -> Point {
    Point {
      x: self.x + other.x,
      y: self.y + other.y,
    }
  }
}
println!("{:? }", Point { x: 1, y: 0 } + Point { x: 2, y: 3 });
// Point { x: 3, y: 3 }
Copy the code

Let’s take a look at the Add trait:

// Note that the default RHS type is defined as itself, that is, the type that implements Add above
// For example, 1 + 2 RHS is the type of 1, i.e. I32
trait Add<RHS = Self> {
  type Output;
  fn add(self, rhs: RHS) -> Self::Output;
}
Copy the code

Of course we can specify the type manually:

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

// Specify Meters to the right of + when using the + operator
impl Add<Meters> for Millimeters {
  type Output = Millimeters;
  fn add(self, other: Meters) -> Millimeters {
    Millimeters(self.0 + (other.0 * 1000))}}println!("{:? }", Millimeters(1) + Meters(2));
// Millimeters(2001)
Copy the code

Fully qualified syntax for disambiguation:

When we implement multiple traits for a structure, there is no guarantee that those traits have the same methods in them, and the same methods may already exist in the structure:

struct Human;

impl Human {
  // Human constructs their own methods
  fn fly(&self) {
    println!("Wave your arms."); }}trait Pilot {
  // The Pilot trait also has the fly method
  fn fly(&self);
}
trait Wizard {
  // The Wizard trait also has the fly method
  fn fly(&self);
}
// Implement Pilot for Human
impl Pilot for Human {
  fn fly(&self) {
    println!("This is your captain speaking."); }}// Implement Wizard for Human
impl Wizard for Human {
  fn fly(&self) {
    println!("Take off!"); }}let person = Human;

// Methods implemented by Human are called by default
person.fly(); // Wave your arms
Copy the code

Manually specify an explicitly called method, a bit like the call method of a JS function:

Human::fly(&person); // Wave your arms
Pilot::fly(&person); // This is your captain speaking
Wizard::fly(&person); / / take off!
Copy the code

Associative functions are part of traits, but without the self argument, they can be understood as static methods of class in JS (that is, methods on classes, not prototype methods, such as Object.create()). When two types of the same scope implement the same trait, rust cannot calculate which one we expect to use unless fully qualified syntax is used:

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")}}// Dog's own implementation of the baby_name method is still called by default
println!("The dog's name is {}", Dog::baby_name());
// The dog's name is Spot

println!("The dog's name is {}", Animal::baby_name());
Animal::baby_name is an association function with no self argument, not a method.
// So Rust cannot infer which implementation of Animal::baby_name we want to call

// Use fully qualified syntax to resolve
println!("The dog's name is {}", <Dog as Animal>::baby_name());
// The dog's name is puppy
Copy the code

Super traits for attaching the functionality of another trait to a trait

Sometimes the functions of A trait B are used in A trait. In this case, we need to make the function of the current A trait depend on B trait to be realized at the same time. The dependent B trait is also the supertrait of the current A trait:

#[derive(Debug)]
struct Position(u32.u32);
trait Logger: std::fmt::Debug { // Logger implementation depends on Debug implementation
  fn log(&self) {
    println!("position: {:? }".self); }}impl Logger for Position {} Logger does not implement Debug
Copy the code

Simply implement Debug for Logger using derivation:

#[derive(Debug)]
struct Position(u32.u32);
trait Logger: std::fmt::Debug {
  fn log(&self) {
    println!("position: {:? }".self); }}impl Logger for Position {}
let position = Position(1.2);
position.log(); // position: Position(1, 2)
Copy the code

Implement external traits on external types using the NewType pattern

We discussed the limitations of trait implementation earlier: We can only implement a trait for a type if either of the types and corresponding traits is defined in a local package (orphan rule). We can actually use the NewType pattern to subtly circumvent this limitation. Let’s try implementing the Display trait for Vec

:

impl std::fmt::Display for Vec<String> {};
// Error because Display and Vec are externally defined
Copy the code

Create a Wrapper type to wrap Vec

so that you can implement the Display trait:

impl std::fmt::Display for Wrapper {
  fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
    write!(f, "[the] {}".self.0.join(",")) // Call self.0, that is, Vec
      
        to use Display
      }}let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w); // w = [hello, world]
Copy the code

Cover: Follow Tina to draw America

Read more about the latest chapter on our official account