Tour of Rust’s Standard Library Traits github.com/pretzelhamm… Praying for Rust

Contents ✅ ⏰

  • The introduction ✅
  • Trait based ✅
  • Automatic Trait ✅
  • Generic Trait ✅
  • Formatting Trait ✅
  • Operator Trait ⏰ = > ✅
  • Conversion Trait ⏰
  • Error handling ⏰
  • The iterator traits ⏰
  • The I/O Trait ⏰
  • Conclusion ⏰

Arithmetic Traits (Arithmetic Traits)

Trait(s) Category (Category) Operator(s) Description:
Add The arithmetic + add
AddAssign The arithmetic + = Add and assign
BitAnd The arithmetic & Bitwise and
BitAndAssign The arithmetic & = Assignment by bitwise and union
BitXor The arithmetic ^ The bitwise exclusive or
BitXorAssign The arithmetic ^ = Bitwise xor union assignment
Div The arithmetic / In addition to
DivAssign The arithmetic / = In addition to the assignment
Mul The arithmetic * take
MulAssign The arithmetic * = Take and assignment
Neg The arithmetic - A dollar complementation
Not The arithmetic ! Unary logic inverse
Rem The arithmetic % For more than
RemAssign The arithmetic % = Take the remainder and assign
Shl The arithmetic << Shift to the left
ShlAssign The arithmetic < < = Shift left and assign
Shr The arithmetic >> Moves to the right
ShrAssign The arithmetic > > = Shift right and assign
Sub The arithmetic - Reduction of
SubAssign The arithmetic - = Reduction and assignment

It is not necessary to go through all the arithmetic operators; after all, most of them only work on numeric types. We’ll talk about Add and AddAssign, because the + operator is often overridden to do other things, such as adding an item to a collection or concatenating, so that we can start with the most interesting places without having to repeat ourselves.

Add & AddAssign

trait Add<Rhs = Self> {
    type Output;
    fn add(self, rhs: Rhs) -> Self::Output;
}
Copy the code

The Add

type can be added to the Rhs type and produce a T as Output.
,>

Add ,>

#[derive(Clone, Copy)]
struct Point {
    x: i32,
    y: i32,}impl Add for Point {
    type Output = Point;
    fn add(self, rhs: Point) -> Point {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let p3 = p1 + p2;
    assert_eq!(p3.x, p1.x + p2.x); / / ✅
    assert_eq!(p3.y, p1.y + p2.y); / / ✅
}

Copy the code

But what if we only have a reference to Point? Can we add them? Let’s try:

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let p3 = &p1 + &p2; / / ❌
}
Copy the code

Obviously not, the compiler throws the following prompt:

error[E0369]: cannot add `&Point` to `&Point`
  --> src/main.rs:50:25
   |
50 |     let p3: Point = &p1 + &p2;
   |                     --- ^ --- &Point
   |                     |
   |                     &Point
   |
   = note: an implementation of `std::ops::Add` might be missing for `&Point`

Copy the code

In Rust’s type system, T, &T, &mut T are all considered completely different types for a given type T, which means we have to provide implementations of traits for each of them. Let’s implement Add for &point:

impl Add for &Point {
    type Output = Point;
    fn add(self, rhs: &Point) -> Point {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let p3 = &p1 + &p2; / / ✅
    assert_eq!(p3.x, p1.x + p2.x); / / ✅
    assert_eq!(p3.y, p1.y + p2.y); / / ✅
}

Copy the code

Still, something didn’t feel right. We implemented two Adds for Point and &Point, which happen to do the same thing today, but we can’t guarantee that will happen in the future. For example, suppose we decide that when we Add two points, we want to create a Line type containing both points instead of creating a new Point, then we update the implementation of Add:

use std::ops::Add;

#[derive(Copy, Clone)]
struct Point {
    x: i32,
    y: i32,}#[derive(Copy, Clone)]
struct Line {
    start: Point,
    end: Point,
}

// we updated this impl
impl Add for Point {
    type Output = Line;
    fn add(self, rhs: Point) -> Line {
        Line {
            start: self,
            end: rhs,
        }
    }
}

// but forgot to update this impl, uh oh!
impl Add for &Point {
    type Output = Point;
    fn add(self, rhs: &Point) -> Point {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let line: Line = p1 + p2; / / ✅

    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let line: Line = &p1 + &p2; // ❌ expected Line, found Point
}

Copy the code

Our current Add implementation for &Point created an unnecessary maintenance burden, and we wanted the implementation to automatically match the Point implementation without having to manually maintain updates every time we changed the Point implementation. We want to keep our code as DRY as possible (Don’t Repeat Yourself). Fortunately, this is possible:

// updated, DRY impl
impl Add for &Point {
    type Output = <Point as Add>::Output;
    fn add(self, rhs: &Point) -> Self::Output {
        Point::add(*self, *rhs)
    }
}

fn main() {
    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let line: Line = p1 + p2; / / ✅

    let p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    let line: Line = &p1 + &p2; / / ✅
}

Copy the code

The AddAssign

type allows us to add and assign to the Rhs type. The trait declares the following:

trait AddAssign<Rhs = Self> {
    fn add_assign(&mut self, rhs: Rhs);
}
Copy the code

Take Point and &Point for example:

use std::ops::AddAssign;

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

impl AddAssign for Point {
    fn add_assign(&mut self, rhs: Point) {
        self.x += rhs.x;
        self.y += rhs.y; }}impl AddAssign<&Point> for Point {
    fn add_assign(&mut self, rhs: &Point) {
        Point::add_assign(self, *rhs); }}fn main() {
    let mut p1 = Point { x: 1, y: 2 };
    let p2 = Point { x: 3, y: 4 };
    p1 += &p2;
    p1 += p2;
    assert!(p1.x == 7 && p1.y == 10);
}

Copy the code

Closure Traits

Trait(s) Category (Category) Operator(s) Description:
Fn closure (... args) Immutable closure calls
FnMut closure (... args) Mutable closure calls
FnOnce closure (... args) A one-time closure call

FnOnce, FnMut, & Fn

trait FnOnce<Args> {
    type Output;
    fn call_once(self, args: Args) -> Self::Output;
}

trait FnMut<Args>: FnOnce<Args> {
    fn call_mut(&mut self, args: Args) -> Self::Output;
}

trait Fn<Args>: FnMut<Args> {
    fn call(&self, args: Args) -> Self::Output;
}

Copy the code

These traits exist, but in Rust of stable, we cannot implement them for our own types. The only type we can create that implements these traits is a closure. A closure decides whether it implements FnOnce, FnMut, or Fn based on what it captures from the environment.

The FnOnce closure can only be called once, because it consumes certain values during execution:

fn main() {
    let range = 0.10;
    let get_range_count = || range.count();
    assert_eq!(get_range_count(), 10); / / ✅
    get_range_count(); / / ❌
}

Copy the code

The.count() method on an iterator consumes the iterator, so it can only be called once. Therefore, our closure can only be called once. That’s why we get the following error when we try to call it a second time:

error[E0382]: use of moved value: `get_range_count`
 --> src/main.rs:5:5| 4 | assert_eq! (get_range_count(), 10); | ----------------- `get_range_count` moved due to this call 5 | get_range_count(); | ^^^^^^^^^^^^^^^ value used here after move | note: closure cannot be invoked more than once because it moves the variable `range` out of its environment --> src/main.rs:3:30
  |
3 |     let get_range_count = || range.count();
  |                              ^^^^^
note: this value implements `FnOnce`, which causes it to be moved when called
 --> src/main.rs:4:16| 4 | assert_eq! (get_range_count(), 10); | ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^ ^Copy the code

The FnMut closure can be called multiple times and can modify the variables it captures from the environment. We could say FnMut was ateful or stateful. Here is an example closure that filters all non-ascending values by tracing the minimum it sees from an iterator.

fn main() {
    let nums = vec![0.4.2.8.10.7.15.18.13];
    let mut min = i32::MIN;
    let ascending = nums.into_iter().filter(|&n| {
        if n <= min {
            false
        } else {
            min = n;
            true
        }
    }).collect::<VecThe < _ > > ();assert_eq!(vec![0.4.8.10.15.18], ascending); / / ✅
}

Copy the code

FnOnce takes ownership of its arguments and can only be called once, whereas FnMut only requires mutable references to arguments and can be called multiple times. FnMut refines FnOnce in this sense. FnMut can be used anywhere FnOnce can be used.

The Fn closure can also be called multiple times, but it cannot modify variables captured from the environment. We can say that Fn closures have no side effects or are stateless. Here is an example of filtering out all numbers from an iterator that are less than a variable on the stack that was captured in the environment:

fn main() {
    let nums = vec![0.4.2.8.10.7.15.18.13];
    let min = 9;
    let greater_than_9 = nums.into_iter().filter(|&n| n > min).collect::<VecThe < _ > > ();assert_eq!(vec![10.15.18.13], greater_than_9); / / ✅
}

Copy the code

Fn refines FnMut in the sense that FnMut requires mutable references and can be called multiple times, while Fn requires immutable references and can be called multiple times. Fn can be used anywhere FnMut can be used, including, of course, where FnOnce can be used.

If a closure does not capture any variables from the environment, it is technically not a closure, but rather an inline function that is anonymously declared, and can be used and passed as a normal function pointer (Fn), including where FnMut and FnOnce can be used.

fn add_one(x: i32) - >i32 {
    x + 1
}

fn main() {
    let mut fn_ptr: fn(i32) - >i32 = add_one;
    assert_eq!(fn_ptr(1), 2); / / ✅

    // capture-less closure cast to fn pointer
    fn_ptr = |x| x + 1; // same as add_one
    assert_eq!(fn_ptr(1), 2); / / ✅
}

Copy the code

Here is an example of passing a normal function pointer instead of a closure:

fn main() {
    let nums = vec![-1.1, -2.2, -3.3];
    let absolutes: Vec<i32> = nums.into_iter().map(i32::abs).collect();
    assert_eq!(vec![1.1.2.2.3.3], absolutes); / / ✅
}

Copy the code

Other Traits (Traits)

Trait(s) Category (Category) Operator(s) Description:
Deref other * Immutable dereference
DerefMut other * Variable dereference
Drop other Type destructor
Index other [] Immutable index
IndexMut other [] The variable index
RangeBounds other . interval
trait Deref {
    type Target:?Sized;
    fn deref(&self) -> &Self::Target;
}

trait DerefMut: Deref {
    fn deref_mut(&mut self) - > &mut Self::Target;
}
Copy the code

The Deref

type can be dereferenced to type T using the * operator. This has obvious use cases in smart pointer types like Box and Rc. However, we rarely see this kind of explicit dereference in Rust code because Rust has a feature called deref coercion.

Rust automatically dereferences types when they are passed as a function parameter, returned from a function, or as part of a method call. This also explains why we can pass &string and &vec

in a function that expects &str and &[T], because String implements Deref

and Vec

implements Deref

.



Deref and DerefMut should only be implemented for smart pointer types. The most common way people misuse and abuse these traits is by trying to stuff OOP (Object-oriented programming) -style data inheritance into Rust. That’s not going to work. Rust is not OOP. Let’s run some tests to see where, how and why it doesn’t work. Let’s start with the following example:

use std::ops::Deref;

struct Human {
    health_points: u32,}enum Weapon {
    Spear,
    Axe,
    Sword,
}

// a Soldier is just a Human with a Weapon
struct Soldier {
    human: Human,
    weapon: Weapon,
}

impl Deref for Soldier {
    type Target = Human;
    fn deref(&self) -> &Human {
        &self.human
    }
}

enum Mount {
    Horse,
    Donkey,
    Cow,
}

// a Knight is just a Soldier with a Mount
struct Knight {
    soldier: Soldier,
    mount: Mount,
}

impl Deref for Knight {
    type Target = Soldier;
    fn deref(&self) -> &Soldier {
        &self.soldier
    }
}

enum Spell {
    MagicMissile,
    FireBolt,
    ThornWhip,
}

// a Mage is just a Human who can cast Spells
struct Mage {
    human: Human,
    spells: Vec<Spell>,
}

impl Deref for Mage {
    type Target = Human;
    fn deref(&self) -> &Human {
        &self.human
    }
}

enum Staff {
    Wooden,
    Metallic,
    Plastic,
}

// a Wizard is just a Mage with a Staff
struct Wizard {
    mage: Mage,
    staff: Staff,
}

impl Deref for Wizard {
    type Target = Mage;
    fn deref(&self) -> &Mage {
        &self.mage
    }
}

fn borrows_human(human: &Human) {}
fn borrows_soldier(soldier: &Soldier) {}
fn borrows_knight(knight: &Knight) {}
fn borrows_mage(mage: &Mage) {}
fn borrows_wizard(wizard: &Wizard) {}

fn example(human: Human, soldier: Soldier, knight: Knight, mage: Mage, wizard: Wizard) {
    // all types can be used as Humans
    borrows_human(&human);
    borrows_human(&soldier);
    borrows_human(&knight);
    borrows_human(&mage);
    borrows_human(&wizard);
    // Knights can be used as Soldiers
    borrows_soldier(&soldier);
    borrows_soldier(&knight);
    // Wizards can be used as Mages
    borrows_mage(&mage);
    borrows_mage(&wizard);
    // Knights & Wizards passed as themselves
    borrows_knight(&knight);
    borrows_wizard(&wizard);
}

Copy the code

At first glance, the above code seems fine! But, on closer inspection, it’s not so good. First, the dereference cast only works on references, so it doesn’t work when we want to pass ownership:

fn takes_human(human: Human) {}

fn example(human: Human, soldier: Soldier, knight: Knight, mage: Mage, wizard: Wizard) {
    // all types CANNOT be used as Humans
    takes_human(human);
    takes_human(soldier); / / ❌
    takes_human(knight); / / ❌
    takes_human(mage); / / ❌
    takes_human(wizard); / / ❌
}

Copy the code

In addition, dereference casts do not work in a generic context. Suppose we implement a trait only on humans:

trait Rest {
    fn rest(&self);
}

impl Rest for Human {
    fn rest(&self) {}}fn take_rest<T: Rest>(rester: &T) {
    rester.rest()
}

fn example(human: Human, soldier: Soldier, knight: Knight, mage: Mage, wizard: Wizard) {
    // all types CANNOT be used as Rest types, only Human
    take_rest(&human);
    take_rest(&soldier); / / ❌
    take_rest(&knight); / / ❌
    take_rest(&mage); / / ❌
    take_rest(&wizard); / / ❌
}

Copy the code

And, while dereference casts can be used in many scenarios, they are not a panacea. It does not work on operands, even though operators are just syntactic sugar for method calls. Suppose we want the Mage to learn Spell via the += operator:

impl DerefMut for Wizard {
    fn deref_mut(&mut self) - > &mut Mage {
        &mut self.mage
    }
}

impl AddAssign<Spell> for Mage {
    fn add_assign(&mut self, spell: Spell) {
        self.spells.push(spell); }}fn example(mut mage: Mage, mut wizard: Wizard, spell: Spell) {
    mage += spell;
    wizard += spell; // ❌ wizard not coerced to mage here
    wizard.add_assign(spell); Oof, we have to call it like this 🤦
}

Copy the code

In programming languages with OOP style data inheritance, the value of self in a method is always equal to the type calling the method, but in Rust, the value of self is always equal to the type implementing the method:

struct Human {
    profession: &'static str,
    health_points: u32,}impl Human {
    // self will always be a Human here, even if we call it on a Soldier
    fn state_profession(&self) {
        println!("I'm a {}!".self.profession); }}struct Soldier {
    profession: &'static str,
    human: Human,
    weapon: Weapon,
}

fn example(soldier: &Soldier) {
    assert_eq!("servant", soldier.human.profession);
    assert_eq!("spearman", soldier.profession);
    soldier.human.state_profession(); // prints "I'm a servant!"
    soldier.state_profession(); // still prints "I'm a servant!" 🤦
}

Copy the code

The pitfalls above are shocking when implementing Deref or DerefMut on a new type. Suppose we want to create a SortedVec type, which is just a Vec but ordered. Here’s how we might do it:

struct SortedVec<T: Ord> (Vec<T>);

impl<T: Ord> SortedVec<T> {
    fn new(mut vec: Vec<T>) -> Self {
        vec.sort();
        SortedVec(vec)
    }
    fn push(&mut self, t: T) {
        self.0.push(t);
        self.0.sort(); }}Copy the code

Obviously, we can’t implement DerefMut

> here, otherwise anyone using SortedVec could easily break the sorted order. But is it safe to implement Deref

>? Try to find bugs in the following programs:

use std::ops::Deref;

struct SortedVec<T: Ord> (Vec<T>);

impl<T: Ord> SortedVec<T> {
    fn new(mut vec: Vec<T>) -> Self {
        vec.sort();
        SortedVec(vec)
    }
    fn push(&mut self, t: T) {
        self.0.push(t);
        self.0.sort(); }}impl<T: Ord> Deref for SortedVec<T> {
    type Target = Vec<T>;
    fn deref(&self) - > &Vec<T> {
        &self.0}}fn main() {
    let sorted = SortedVec::new(vec![2.8.6.3]);
    sorted.push(1);
    let sortedClone = sorted.clone();
    sortedClone.push(4);
}

Copy the code

We haven’t implemented Clone for SortedVec, so when we call the.clone() method, the compiler resolves it to a method call on Vec using a dereference cast, so it returns a Vec instead of a SortedVec!

fn main() {
    let sorted: SortedVec<i32> = SortedVec::new(vec![2.8.6.3]);
    sorted.push(1); // still sorted

    // calling clone on SortedVec actually returns a Vec 🤦
    let sortedClone: Vec<i32> = sorted.clone();
    sortedClone.push(4); SortedClone no longer sorted 💀
}

Copy the code

Either way, the above limitations, constraints, or traps are not Rust’s fault, because Rust was never designed to be an OO (object-oriented) language or to support OOP (Object-oriented programming) patterns in the first place.

The point of this section is not to try to get clever with Deref and DerefMut implementations. They only work with unstable smart pointer types and are currently only available in the standard library because smart pointer types currently require the feature unstable and compiler magic to work. If we want functionality and behavior similar to Deref and DerefMut, we can look at AsRef and AsMut, which will be mentioned later.

Index & IndexMut

trait Index<Idx: ?Sized> {
    type Output:?Sized;
    fn index(&self, index: Idx) -> &Self::Output;
}

trait IndexMut<Idx>: Index<Idx> where Idx: ?Sized {
    fn index_mut(&mut self, index: Idx) -> &mut Self::Output;
}

Copy the code

We can Index [] to Index

with T. The Index operation returns &u. For syntactic purposes, the compiler automatically inserts a dereference operator * before the return value of the index operation:
,>

fn main() {
    // Vec<i32> impls Index<usize, Output = i32> so
    // indexing Vec<i32> should produce &i32s and yet...
    let vec = vec![1.2.3.4.5];
    let num_ref: &i32 = vec[0]; // ❌ expected &i32 found i32

    // above line actually desugars to
    let num_ref: &i32 = *vec[0]; // ❌ expected &i32 found i32

    // both of these alternatives work
    let num: i32 = vec[0]; / / ✅
    let num_ref = &vec[0]; / / ✅
}

Copy the code

To show how we can implement Index ourselves, here is an interesting example that shows how we can implement wrap indexes and non-negative indexes on Vec using a new type and Indextrait:

use std::ops::Index;

struct WrappingIndex<T>(Vec<T>);

impl<T> Index<usize> for WrappingIndex<T> {
    type Output = T;
    fn index(&self, index: usize) -> &T {
        &self.0[index % self.0.len()]
    }
}

impl<T> Index<i128> for WrappingIndex<T> {
    type Output = T;
    fn index(&self, index: i128) -> &T {
        let self_len = self.0.len() as i128;
        let idx = (((index % self_len) + self_len) % self_len) as usize;
        &self.0[idx]
    }
}

#[test] / / ✅
fn indexes() {
    let wrapping_vec = WrappingIndex(vec![1.2.3]);
    assert_eq!(1, wrapping_vec[0_usize]);
    assert_eq!(2, wrapping_vec[1_usize]);
    assert_eq!(3, wrapping_vec[2_usize]);
}

#[test] / / ✅
fn wrapping_indexes() {
    let wrapping_vec = WrappingIndex(vec![1.2.3]);
    assert_eq!(1, wrapping_vec[3_usize]);
    assert_eq!(2, wrapping_vec[4_usize]);
    assert_eq!(3, wrapping_vec[5_usize]);
}

#[test] / / ✅
fn neg_indexes() {
    let wrapping_vec = WrappingIndex(vec![1.2.3]);
    assert_eq!(1, wrapping_vec[-3_i128]);
    assert_eq!(2, wrapping_vec[-2_i128]);
    assert_eq!(3, wrapping_vec[-1_i128]);
}

#[test] / / ✅
fn wrapping_neg_indexes() {
    let wrapping_vec = WrappingIndex(vec![1.2.3]);
    assert_eq!(1, wrapping_vec[-6_i128]);
    assert_eq!(2, wrapping_vec[-5_i128]);
    assert_eq!(3, wrapping_vec[-4_i128]);
}

Copy the code

There is no requirement that Idx be a numeric type or a Range, it can also be an enumeration! Here is an example of using basketball positions to retrieve players on a team:

use std::ops::Index;

enum BasketballPosition {
    PointGuard,
    ShootingGuard,
    Center,
    PowerForward,
    SmallForward,
}

struct BasketballPlayer {
    name: &'static str,
    position: BasketballPosition,
}

struct BasketballTeam {
    point_guard: BasketballPlayer,
    shooting_guard: BasketballPlayer,
    center: BasketballPlayer,
    power_forward: BasketballPlayer,
    small_forward: BasketballPlayer,
}

impl Index<BasketballPosition> for BasketballTeam {
    type Output = BasketballPlayer;
    fn index(&self, position: BasketballPosition) -> &BasketballPlayer {
        match position {
            BasketballPosition::PointGuard => &self.point_guard,
            BasketballPosition::ShootingGuard => &self.shooting_guard,
            BasketballPosition::Center => &self.center,
            BasketballPosition::PowerForward => &self.power_forward,
            BasketballPosition::SmallForward => &self.small_forward,
        }
    }
}

Copy the code

Drop

trait Drop {
    fn drop(&mut self);
}
Copy the code

If a type implements Drop, then Drop will be called before the type leaves scope but is destroyed. We rarely need to implement this for our types, but it is useful if a type holds some external resources that need to be cleaned up when the type is destroyed.

There is a BufWriter type in the library that allows us to buffer written data into a Write type. But what if the BufWriter is destroyed before its contents are brushed into the underlying Write type? Fortunately that’s not possible! BufWriter implements the Droptrait, so whenever it leaves scope, Flush is always called!

impl<W: Write> Drop for BufWriter<W> {
    fn drop(&mut self) {
        self.flush_buf(); }}Copy the code

In addition, Mutexs in Rust do not have unlock() methods because they are not required! Calling lock() on Mutex returns a MutexGuard, which automatically unlocks Mutex when the MutexGuard leaves scope, thanks to its Drop implementation:

impl<T: ?Sized> Drop for MutexGuard<'_, T> {
    fn drop(&mut self) {
        unsafe {
            self.lock.inner.raw_unlock(); }}}Copy the code

In general, if you’re implementing an abstraction of a type of resource that needs to be cleaned up after use, it’s time to make the most of the Drop trait.