Tour of Rust’s Standard Library Traits 原文 名 称 : Tour of Rust’s Standard Library Traits Went down to my Praying platform

Directory of contents (✅)

  • The introduction ✅
  • Trait based ✅
    • Trait item ✅
      • The Self ✅
      • Function ✅
      • Methods ✅
      • Association type ✅
      • The generic type is ✅
      • Generic types vs association types ✅
    • Scope ✅
    • The derived macro ✅
    • Default implementation (Default Impls) ✅
    • Generic override implementation ✅
    • Subtraits & SuperTraits ✅
    • Trait object ✅
    • Mark Trait ✅
    • Automatic Trait ✅
    • Unsafe Traits ✅
  • Automatic Trait ⏰
  • Generic Trait ⏰
  • Formatting Trait ⏰
  • Operator Trait ⏰
  • Conversion Trait ⏰
  • Error handling ⏰
  • The iterator traits ⏰
  • The I/O Trait ⏰
  • Conclusion ⏰

The introduction

Have you ever wondered how these traits are different?

  • Deref<Traget=T>.AsRef<T>, as well asBorrow<T>?
  • Clone.Copy, andToOwned?
  • From<T>Into<T>?
  • TryFrom<&str>FromStr?
  • FnOnce.FnMut.Fnandfn?

Or have you asked yourself these questions:

  • “When do I use associated types and when do I use generic types in traits?”

  • What is generic Blanket Impls?

  • “How do subtraits and supertraits work?”

  • “Why doesn’t this trait have any approach?”

Then this article is for you! It answers questions including, but not limited to, all of the above. Together, we’ll take a quick look at all the most popular and commonly used traits in the Rust standard library.

You can read this article in chapter order, also can jump you are most interested in trait, because each trait to the beginning of the chapter has a list of links to the front section, you should read these links, in order to have enough background knowledge to understand the current chapter explain (: I’m sorry, temporarily unable to provide links to jump in the translation).

Triat basis

We’ll cover enough of the basics so that the rest of the article can be streamlined without having to explain the same concepts over and over again because they appear in different traits.

Trait (Item)

A Trait item is any item contained in a Trait declaration.

Self

Self always refers to the implementation type.

trait Trait {
    // always returns i32
    fn returns_num() - >i32;

    // returns implementing type
    fn returns_self() - >Self;
}

struct SomeType;
struct OtherType;

impl Trait for SomeType {
    fn returns_num() - >i32 {
        5
    }

    // Self == SomeType
    fn returns_self() - >Self {
        SomeType
    }
}

impl Trait for OtherType {
    fn returns_num() - >i32 {
        6
    }

    // Self == OtherType
    fn returns_self() - >Self {
        OtherType
    }
}
Copy the code

Function (Function)

A Trait function is any function whose first argument is not the self keyword.

trait Default {
    // function
    fn default() - >Self;
}
Copy the code

Trait functions can be called from the namespace of either the Trait or the implementation type.

fn main() {
    let zero: i32 = Default::default();
    let zero = i32::default();
}
Copy the code

Method (Method)

The Trait method uses the self keyword as its first argument and the type of self is one of self,& self,& mut self. The type self can also be wrapped by Box, Rc, Arc, or Pin.

trait Trait {
    // methods
    fn takes_self(self);
    fn takes_immut_self(&self);
    fn takes_mut_self(&mut self);

    // above methods desugared
    fn takes_self(self: Self);
    fn takes_immut_self(self: &Self);
    fn takes_mut_self(self: &mut Self);
}

// example from standard library
trait ToString {
    fn to_string(&self) - >String;
}
Copy the code

Trait methods can be used by using points (.) on implementation types. Operator.

fn main() {
    let five = 5.to_string();
}
Copy the code

In addition, trait methods can be called from a namespace by either a trait or an implementation type, just like a function.

fn main() {
    let five = ToString::to_string(&5);
    let five = i32::to_string(&5);
}
Copy the code

Associated Types

Traits can have association types. Association types come into play when we need to use a type other than Self in a function signature, but want that type to be selected by the implementer rather than hard-coded into the trait declaration.

trait Trait {
    type AssociatedType;
    fn func(arg: Self::AssociatedType);
}

struct SomeType;
struct OtherType;

// any type implementing Trait can
// choose the type of AssociatedType

impl Trait for SomeType {
    type AssociatedType = i8; // chooses i8
    fn func(arg: Self::AssociatedType) {}
}

impl Trait for OtherType {
    type AssociatedType = u8; // chooses u8
    fn func(arg: Self::AssociatedType) {}
}

fn main() {
    SomeType::func(-1_i8); // can only call func with i8 on SomeType
    OtherType::func(1_u8); // can only call func with u8 on OtherType
}
Copy the code

Generic Parameters

Generic Parameter refers to generic type parameters, generic Lifetime parameters, and generic const parameters. Because these are tricky to say, people often refer to them simply as “generic type,” “lifetime,” and “generic const.” Since none of the library traits we’ll discuss use generic constants, they are outside the scope of this article.

You can generalize a trait declaration using parameters.

// trait declaration generalized with lifetime & type parameters
trait Trait<'a, T> {
    // signature uses generic type
    fn func1(arg: T);

    // signature uses lifetime
    fn func2(arg: &'a i32);

    // signature uses generic type & lifetime
    fn func3(arg: &'a T);
}

struct SomeType;

impl<'a> Trait<'a.i8> for SomeType {
    fn func1(arg: i8) {}
    fn func2(arg: &'a i32) {}
    fn func3(arg: &'a i8) {}}impl<'b> Trait<'b.u8> for SomeType {
    fn func1(arg: u8) {}
    fn func2(arg: &'b i32) {}
    fn func3(arg: &'b u8) {}}Copy the code

Generics can have default values, the most common one being Self, but any type can be used as a default.

// make T = Self by default
trait Trait<T = Self> {
    fn func(t: T) {}
}

// any type can be used as the default
trait Trait2<T = i32> {
    fn func2(t: T) {}
}

struct SomeType;

// omitting the generic type will
// cause the impl to use the default
// value, which is Self here
impl Trait for SomeType {
    fn func(t: SomeType) {}
}

// default value here is i32
impl Trait2 for SomeType {
    fn func2(t: i32) {}}// the default is overridable as we'd expect
impl Trait<String> for SomeType {
    fn func(t: String) {}}// overridable here too
impl Trait2<String> for SomeType {
    fn func2(t: String) {}}Copy the code

In addition to being able to parameterize traits, we can also parameterize individual functions and methods.

trait Trait {
    fn func<'a, T>(t: &'a T);
}
Copy the code

Generic types vs association types

Both generic types and association types leave the decision of which specific types to use in traits’ functions and methods to the implementer, so this section explains when to use generic types and when to use association types.

The general rule of thumb is:

  • Use association types when there should be only one implementation of a trait per type.

  • Use generic types when each type may have multiple implementations of traits.

Let’s say we want to define a trait called Add that allows us to Add values. Here is an initial design and implementation using only association types.

trait Add {
    type Rhs;
    type Output;
    fn add(self, rhs: Self::Rhs) -> Self::Output;
}

struct Point {
    x: i32,
    y: i32,}impl Add for Point {
    type Rhs = 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: 1 };
    let p2 = Point { x: 2, y: 2 };
    let p3 = p1.add(p2);
    assert_eq!(p3.x, 3);
    assert_eq!(p3.y, 3);
}
Copy the code

Suppose we now want to add the ability to add i32 to a Point, where the members x and y in Point are added to i32.

trait Add {
    type Rhs;
    type Output;
    fn add(self, rhs: Self::Rhs) -> Self::Output;
}

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

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

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

    let p1 = Point { x: 1, y: 1 };
    let int2 = 2;
    let p3 = p1.add(int2); / / ❌
    assert_eq!(p3.x, 3);
    assert_eq!(p3.y, 3);
}
Copy the code

The above code throws an error:

error[E0119]: conflicting implementations of trait `Add` for type `Point`:
  --> src/main.rs:23:1
   |
12 | impl Add for Point {
   | ------------------ first implementation here
...
23 | impl Add for Point {
   | ^^^^^^^^^^^^^^^^^^ conflicting implementation for `Point`
Copy the code

Because the Add trait is not parameterized by any generic type, we can only implement the trait once per type, which means we can only select both Rhs and Output types at one time! In order for both the Point and i32 types to add to Point, we had to refactor the Rhs from an associated type to a generic type, which allowed us to implement the trait for Point multiple times depending on the type parameters of the Rhs.

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

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

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

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

    let p1 = Point { x: 1, y: 1 };
    let int2 = 2;
    let p3 = p1.add(int2); / / ✅
    assert_eq!(p3.x, 3);
    assert_eq!(p3.y, 3);
}
Copy the code

Let’s say we added a new type called Line that contains two points, and now we have a context in our program where the addition of two points should produce a Line instead of another Point. This is not feasible in our current Add trait design because Output is an association type, but we implement this new requirement by refactoring Output from an association type to a generic type.

trait Add<Rhs, Output> {
    fn add(self, rhs: Rhs) -> Output;
}

struct Point {
    x: i32,
    y: i32,}impl Add<Point, Point> for Point {
    fn add(self, rhs: Point) -> Point {
        Point {
            x: self.x + rhs.x,
            y: self.y + rhs.y,
        }
    }
}

impl Add<i32, Point> for Point {
    fn add(self, rhs: i32) -> Point {
        Point {
            x: self.x + rhs,
            y: self.y + rhs,
        }
    }
}

struct Line {
    start: Point,
    end: Point,
}

impl Add<Point, Line> for Point { / / ✅
    fn add(self, rhs: Point) -> Line {
        Line {
            start: self,
            end: rhs,
        }
    }
}

fn main() {
    let p1 = Point { x: 1, y: 1 };
    let p2 = Point { x: 2, y: 2 };
    let p3: Point = p1.add(p2);
    assert!(p3.x == 3 && p3.y == 3);

    let p1 = Point { x: 1, y: 1 };
    let int2 = 2;
    let p3 = p1.add(int2);
    assert!(p3.x == 3 && p3.y == 3);

    let p1 = Point { x: 1, y: 1 };
    let p2 = Point { x: 2, y: 2 };
    let l: Line = p1.add(p2); / / ✅
    assert!(l.start.x == 1 && l.start.y == 1 && l.end.x == 2 && l.end.y == 2)}Copy the code

So, which Add trait is the best? It depends on the requirements in your application! Put in the right context, they’re all good.

Scope (Scope)

A trait can be used only if it is in scope. Most Rustaceans learn this the hard way the first time they try to Write an I/ O-related program, because the Read and Write traits are not in the library prelude.

use std::fs::File;
use std::io;

fn main() - >Result<(), io::Error> {
    let mut file = File::open("Cargo.toml")? ;let mut buffer = String::new();
    file.read_to_string(&mutbuffer)? ;// ❌ read_to_string not found in File
    Ok(())}Copy the code

Read_to_string (buf: &mut String) is declared in STD :: IO ::Read and implemented by the STD ::fs::File structure, but to call it, STD :: IO ::Read must be in the current scope.

use std::fs::File;
use std::io;
use std::io::Read; / / ✅

fn main() - >Result<(), io::Error> {
    let mut file = File::open("Cargo.toml")? ;let mut buffer = String::new();
    file.read_to_string(&mutbuffer)? ;/ / ✅
    Ok(())}Copy the code

The Standard Library Prelude is a module in The standard Library, that is, STD :: Prelude ::v1, which is automatically imported at The top of every other module, using STD :: Prelude ::v1::*. This way, the following traits will always be in scope, and we don’t need to import them explicitly ourselves because they are part of the presets.

  • AsMut
  • AsRef
  • Clone
  • Copy
  • Default
  • Drop
  • Eq
  • Fn
  • FnMut
  • FnOnce
  • From
  • Into
  • ToOwned
  • IntoIterator
  • Iterator
  • PartialEq
  • PartialOrd
  • Send
  • Sized
  • Sync
  • ToString
  • Ord

Derive Macros

The standard library exports a small number of derived macros that make it easy to implement a trait on a type if all members of that type implement the trait. Derived macros are named after the traits they implement.

  • Clone
  • Copy
  • Debug
  • Default
  • Eq
  • Hash
  • Ord
  • PartialEq
  • PartialOrd

Example:

// macro derives Copy & Clone impl for SomeType
#[derive(Copy, Clone)]
struct SomeType;
Copy the code

Note: Procedural macros are procedural macros, they can be used to do just about anything, there is no mandate that they implement a trait, or that they only work if all members implement the trait, these are just the conventions that procedural macros follow in the library.

Default implementation (Default Impls)

Traits can provide default implementations for their functions and methods.

trait Trait {
    fn method(&self) {
        println!("default impl"); }}struct SomeType;
struct OtherType;

// use default impl for Trait::method
impl Trait for SomeType {}

impl Trait for OtherType {
    // use our own impl for Trait::method
    fn method(&self) {
        println!("OtherType impl"); }}fn main() {
    SomeType.method(); // prints "default impl"
    OtherType.method(); // prints "OtherType impl"
}
Copy the code

This is especially handy if some of the methods in a trait are implemented entirely through other methods of the trait.

trait Greet {
    fn greet(&self, name: &str) - >String;
    fn greet_loudly(&self, name: &str) - >String {
        self.greet(name) + "!"}}struct Hello;
struct Hola;

impl Greet for Hello {
    fn greet(&self, name: &str) - >String {
        format!("Hello {}", name)
    }
    // use default impl for greet_loudly
}

impl Greet for Hola {
    fn greet(&self, name: &str) - >String {
        format!("Hola {}", name)
    }
    // override default impl
    fn greet_loudly(&self, name: &str) - >String {
        let mut greeting = self.greet(name);
        greeting.insert_str(0."¡");
        greeting + "!"}}fn main() {
    println!("{}", Hello.greet("John")); // prints "Hello John"
    println!("{}", Hello.greet_loudly("John")); // prints "Hello John!"
    println!("{}", Hola.greet("John")); // prints "Hola John"
    println!("{}", Hola.greet_loudly("John")); / / prints "¡ Hola John!"
}
Copy the code

Many traits in the standard library provide default implementations for many of their methods.

Generic Blanket Impls

A generic override implementation is one that is implemented on a generic type rather than a concrete type. To explain why and how to use it, let’s start by implementing an is_even method for integer types.

trait Even {
    fn is_even(self) - >bool;
}

impl Even for i8 {
    fn is_even(self) - >bool {
        self % 2_i8= =0_i8}}impl Even for u8 {
    fn is_even(self) - >bool {
        self % 2_u8= =0_u8}}impl Even for i16 {
    fn is_even(self) - >bool {
        self % 2_i16= =0_i16}}// etc

#[test] / / ✅
fn test_is_even() {
    assert!(2_i8.is_even());
    assert!(4_u8.is_even());
    assert!(6_i16.is_even());
    // etc
}
Copy the code

Obviously, the above implementation is verbose. Moreover, all of our implementations are pretty much the same. Also, if Rust decides to add more integer types in the future, we’ll have to go back to this code and update it with the new integer types. We can solve all of these problems by using generic override implementations.

use std::fmt::Debug;
use std::convert::TryInto;
use std::ops::Rem;

trait Even {
    fn is_even(self) - >bool;
}

// generic blanket impl
impl<T> Even for T
where
    T: Rem<Output = T> + PartialEq<T> + Sized.u8: TryInto<T>,
    <u8 as TryInto<T>>::Error: Debug,
{
    fn is_even(self) - >bool {
        // these unwraps will never panic
        self % 2.try_into().unwrap() == 0.try_into().unwrap()
    }
}

#[test] / / ✅
fn test_is_even() {
    assert!(2_i8.is_even());
    assert!(4_u8.is_even());
    assert!(6_i16.is_even());
    // etc
}
Copy the code

Unlike default implementations, generic override implementations provide implementations of methods, so they cannot be overridden.

use std::fmt::Debug;
use std::convert::TryInto;
use std::ops::Rem;

trait Even {
    fn is_even(self) - >bool;
}

impl<T> Even for T
where
    T: Rem<Output = T> + PartialEq<T> + Sized.u8: TryInto<T>,
    <u8 as TryInto<T>>::Error: Debug,
{
    fn is_even(self) - >bool {
        self % 2.try_into().unwrap() == 0.try_into().unwrap()
    }
}

impl Even for u8 { / / ❌
    fn is_even(self) - >bool {
        self % 2_u8= =0_u8}}Copy the code

The above code throws the following error:

error[E0119]: conflicting implementations of trait `Even` for type `u8`:
  --> src/lib.rs:22:1
   |
10 | / impl<T> Even for T
11 | | where
12 | |     T: Rem<Output = T> + PartialEq<T> + Sized,
13 | |     u8: TryInto<T>,
...  |
19 | |     }
20 | | }
   | |_- first implementation here
21 |
22 |   impl Even for u8 {
   |   ^^^^^^^^^^^^^^^^ conflicting implementation for `u8`
Copy the code

These implementations overlap, so they conflict, so Rust refuses to compile this code to ensure consistency in the trait. Trait consistency means that for any given type, there is at most one implementation of a trait. The rules Rust uses to enforce trait conformance, the meaning of those rules, and the workarounds for those meanings are beyond the scope of this article.

Subtraits & Supertraits

Sub in a subtrait refers to subset, and super in a supertrait refers to superset. If we have the following trait declaration:

trait Subtrait: Supertrait {}
Copy the code

All types that implement a Subtrait are subsets of all types that implement a Supertrait, or vice versa: all types that implement a Supertrait are supersets of all types that implement a Subtrait. Also, the above code is a kind of syntactic sugar that should be expanded to read:

trait Subtrait where Self: Supertrait {}
Copy the code

This is a subtle but important distinction, but understand that the constraint is on Self, the type that implements the Subtrait, not the Subtrait itself. The latter also makes no sense, since trait constraints can only apply to specific types that can implement traits, and traits themselves cannot implement other traits:

trait Supertrait {
    fn method(&self) {
        println!("in supertrait"); }}trait Subtrait: Supertrait {
    // this looks like it might impl or
    // override Supertrait::method but it
    // does not
    fn method(&self) {
        println!("in subtrait")}}struct SomeType;

// adds Supertrait::method to SomeType
impl Supertrait for SomeType {}

// adds Subtrait::method to SomeType
impl Subtrait for SomeType {}

// both methods exist on SomeType simultaneously
// neither overriding or shadowing the other

fn main() {
    SomeType.method(); // ❌ ambiguous method call
    // must disambiguate using fully-qualified syntax
    <SomeType as Supertrait>::method(&st); // ✅ prints "in supertrait"
    <SomeType as Subtrait>::method(&st); // ✅ prints "in subtrait"
}
Copy the code

In addition, there are no clear rules for how a type implements both a subtrait and a supertrait. It can implement other methods in another type of implementation.

trait Supertrait {
    fn super_method(&mut self);
}

trait Subtrait: Supertrait {
    fn sub_method(&mut self);
}

struct CallSuperFromSub;

impl Supertrait for CallSuperFromSub {
    fn super_method(&mut self) {
        println!("in super"); }}impl Subtrait for CallSuperFromSub {
    fn sub_method(&mut self) {
        println!("in sub");
        self.super_method(); }}struct CallSubFromSuper;

impl Supertrait for CallSubFromSuper {
    fn super_method(&mut self) {
        println!("in super");
        self.sub_method(); }}impl Subtrait for CallSubFromSuper {
    fn sub_method(&mut self) {
        println!("in sub"); }}struct CallEachOther(bool);

impl Supertrait for CallEachOther {
    fn super_method(&mut self) {
        println!("in super");
        if self.0 {
            self.0 = false;
            self.sub_method(); }}}impl Subtrait for CallEachOther {
    fn sub_method(&mut self) {
        println!("in sub");
        if self.0 {
            self.0 = false;
            self.super_method(); }}}fn main() {
    CallSuperFromSub.super_method(); // prints "in super"
    CallSuperFromSub.sub_method(); // prints "in sub", "in super"

    CallSubFromSuper.super_method(); // prints "in super", "in sub"
    CallSubFromSuper.sub_method(); // prints "in sub"

    CallEachOther(true).super_method(); // prints "in super", "in sub"
    CallEachOther(true).sub_method(); // prints "in sub", "in super"
}
Copy the code

Hopefully, the above example shows that the relationship between a subtrait and a supertrait can be quite complex. Before introducing the mental model that neatly encapsulates these complexities, let’s quickly review and establish the mental model we use to understand trait constraints on generic types.

fn function<T: Clone>(t: T) {
    // impl
}
Copy the code

Without knowing the implementation of this function, it’s a reasonable guess that t.lone () will be called at some point, because when a generic type is bound by a trait, that means it has a dependency on the trait. The mental model of the relationship between generics and trait constraints is a simple and intuitive one: generics depend on trait constraints.

Now let’s look at Copy’s trait declaration:

trait Copy: Clone {}
Copy the code

The syntax above looks similar to applying a trait constraint on a generic type, except that Copy does not depend on Clone at all. The previous model doesn’t help here. In my opinion, the most concise and elegant mental model for understanding subtraits and supertraits is that subtraits refine their supertraits.

“Refinement” is intentionally vague, because it can mean different things in different contexts:

  • Subtraits may make supertrait methods more specific, faster, and less memory intensive, for example, Copy:Clone;

  • Subtrait is likely to be supertrait method to implement additional guarantee, for example: Eq: PartialEq, word: PartialOrd, ExactSizeIterator: Iterator;

  • A subtrait may make supertrait methods more flexible and easy to invoke, for example: FnMut: FnOnce,Fn: FnMut;

  • Subtrait may expand supertrait and add new methods, such as: DoubleEndedIterator: Iterator, ExactSizeIterator: Iterator.

Trait object

Generics give us compile-time polymorphism, while trait objects give us run-time polymorphism. You can use trait objects to have functions return different types dynamically at run time.

fn example(condition: bool, vec: Vec<i32- > >)Box<dyn Iterator<Item = i32> > {let iter = vec.into_iter();
    if condition {
        // Has type:
        // Box<Map<IntoIter<i32>, Fn(i32) -> i32>>
        // But is cast to:
        // Box<dyn Iterator<Item = i32>>
        Box::new(iter.map(|n| n * 2))}else {
        // Has type:
        // Box<Filter<IntoIter<i32>, Fn(&i32) -> bool>>
        // But is cast to:
        // Box<dyn Iterator<Item = i32>>
        Box::new(iter.filter(|&n| n >= 2))}}Copy the code

Traits also allow us to store multiple types in collections:

use std::f64::consts::PI;

struct Circle {
    radius: f64,}struct Square {
    side: f64
}

trait Shape {
    fn area(&self) - >f64;
}

impl Shape for Circle {
    fn area(&self) - >f64 {
        PI * self.radius * self.radius
    }
}

impl Shape for Square {
    fn area(&self) - >f64 {
        self.side * self.side
    }
}

fn get_total_area(shapes: Vec<Box<dyn Shape>>) -> f64 {
    shapes.into_iter().map(|s| s.area()).sum()
}

fn example() {
    let shapes: Vec<Box<dyn Shape>> = vec![
        Box::new(Circle { radius: 1.0 }), // Box<Circle> cast to Box<dyn Shape>
        Box::new(Square { side: 1.0 }), // Box<Square> cast to Box<dyn Shape>
    ];
    assert_eq!(PI + 1.0, get_total_area(shapes)); / / ✅
}
Copy the code

Trait objects have no size, so they must always be followed by a pointer. We can distinguish between concrete types and trait objects at the type level by the presence of the dyn keyword in the type.

struct Struct;
trait Trait {}

// regular struct
&Struct
Box<Struct>
Rc<Struct>
Arc<Struct>

// trait objects
&dyn Trait
Box<dyn Trait>
Rc<dyn Trait>
Arc<dyn Trait>
Copy the code

Not all traits can be converted to trait objects. A trait is object-safe if and only if it meets the following requirements:

  • Traits that do not requireSelf:Sized
  • All methods of traits are object safe

A trait method is object safe when it meets the following requirements:

  • Method requiresSelf:Sizedor
  • Method uses only one at its receiver locationSelftype

To understand why this is the case is irrelevant to the rest of this article, but if you’re still curious, check out Sizeness in Rust.

Marker Traits

A tag trait is a trait that does not contain a trait. Their job is to define the implementation type “mark” as having certain properties that would otherwise have no way to represent them in the type system.

// Impling PartialEq for a type promises
// that equality for the type has these properties:
// - symmetry: a == b implies b == a, and
// - transitivity: a == b && b == c implies a == c
// But DOES NOT promise this property:
// - reflexivity: a == a
trait PartialEq {
    fn eq(&self, other: &Self) - >bool;
}

// Eq has no trait items! The eq method is already
// declared by PartialEq, but "impling" Eq
// for a type promises this additional equality property:
// - reflexivity: a == a
trait Eq: PartialEq {}

// f64 impls PartialEq but not Eq because NaN ! = NaN
// i32 impls PartialEq & Eq because there's no NaNs :)
Copy the code

Automatic Trait (Auto Trait)

An autotrait means that a type automatically implements the Trait if all its members have implemented it. The meaning of “member” depends on the type, such as the field of a structure, the variable of an enumeration, the element of an array, the item of a tuple, and so on.

All autotraits are tag traits, but not all tag traits are automatic traits. Automatic traits must be tag traits, so the compiler can provide them with an automatic default implementation, which would not be possible if they had any traits.

Example of an automatic trait.

// implemented for types which are safe to send between threads
unsafe auto trait Send {}

// implemented for types whose references are safe to send between threads
unsafe auto trait Sync {}
Copy the code

An Unsafe Trait

A Trait can be marked unsafe to indicate that implementing the Trait might require unsafe code. Both Send and Sync are flagged as unsafe because if they are not automatically implemented, that means they must contain members that are not Send or Sync. If we want to manually flag the types Send and Sync, we as implementors have to be extra careful to make sure there is no data race.