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 ✅
  • Automatic Trait ✅
  • Generic Trait ✅
  • Formatting Trait ✅
  • Operator Trait ✅
  • Conversion Trait ⏰ = > ✅
  • Error handling ⏰=>✅
  • The iterator traits ⏰
  • The I/O Trait ⏰
  • Conclusion ⏰

Conversion Traits

From & Into

trait From<T> {
    fn from(T) -> Self;
}
Copy the code

The From

type allows us to convert T to Self.

trait Into<T> {
    fn into(self) -> T;
}
Copy the code

The Into

type allows us to convert Self to T. They’re like two sides of the same coin. We can only implement From

for our own types, because the implementation of Into

is provided automatically via the Generic blanket IMPl:

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)}}Copy the code

These two traits exist because they allow us to do trait bound in slightly different ways:

fn function<T>(t: T)
where
    // these bounds are equivalent
    T: From<i32>,
    i32: Into<T>
{
    // these examples are equivalent
    let example: T = T::from(0);
    let example: T = 0.into();
}
Copy the code

There are no rules that dictate when to use the former or the latter, so it’s ok to use the most reasonable approach in each situation. Now let’s look at an example:

struct Point {
    x: i32,
    y: i32,}impl From< (i32.i32) >for Point {
    fn from((x, y): (i32.i32)) - >Self {
        Point { x, y }
    }
}

impl From"[i32; 2] >for Point {
    fn from([x, y]: [i32; 2]) -> Self {
        Point { x, y }
    }
}

fn example() {
    / / use the From
    let origin = Point::from((0.0));
    let origin = Point::from([0.0]);

    / / Into use
    let origin: Point = (0.0).into();
    let origin: Point = [0.0].into();
}

Copy the code

This implementation is not symmetric, so if we want to convert points into tuples and arrays, we must explicitly add the following:

struct Point {
    x: i32,
    y: i32,}impl From< (i32.i32) >for Point {
    fn from((x, y): (i32.i32)) - >Self {
        Point { x, y }
    }
}

impl From<Point> for (i32.i32) {
    fn from(Point { x, y }: Point) -> Self {
        (x, y)
    }
}

impl From"[i32; 2] >for Point {
    fn from([x, y]: [i32; 2]) -> Self {
        Point { x, y }
    }
}

impl From<Point> for [i32; 2] {
    fn from(Point { x, y }: Point) -> Self {
        [x, y]
    }
}

fn example() {
    // From (i32, i32) to Point
    let point = Point::from((0.0));
    let point: Point = (0.0).into();

    // From Point to (i32, i32)
    let tuple = <(i32.i32)>::from(point);
    let tuple: (i32.i32) = point.into();

    // From [i32; 2] to Point
    let point = Point::from([0.0]);
    let point: Point = [0.0].into();

    // from Point to [i32; 2]
    let array = <[i32; 2]>::from(point);
    let array: [i32; 2] = point.into();
}

Copy the code

A common use of From

is to thin template code. Let’s say we want to add a Triangle type to our program, which contains three points. Here’s how we can construct it:

struct Point {
    x: i32,
    y: i32,}impl Point {
    fn new(x: i32, y: i32) -> Point {
        Point { x, y }
    }
}

impl From< (i32.i32) >for Point {
    fn from((x, y): (i32.i32)) -> Point {
        Point { x, y }
    }
}

struct Triangle {
    p1: Point,
    p2: Point,
    p3: Point,
}

impl Triangle {
    fn new(p1: Point, p2: Point, p3: Point) -> Triangle {
        Triangle { p1, p2, p3 }
    }
}

impl<P> From<[P; 3] >for Triangle
where
    P: Into<Point>
{
    fn from([p1, p2, p3]: [P; 3]) -> Triangle {
        Triangle {
            p1: p1.into(),
            p2: p2.into(),
            p3: p3.into(),
        }
    }
}

fn example() {
    // Manual construction
    let triangle = Triangle {
        p1: Point {
            x: 0,
            y: 0,
        },
        p2: Point {
            x: 1,
            y: 1,
        },
        p3: Point {
            x: 2,
            y: 2,}};/ / use Point: : new
    let triangle = Triangle {
        p1: Point::new(0.0),
        p2: Point::new(1.1),
        p3: Point::new(2.2),};From<(i32, i32)> for Point
    let triangle = Triangle {
        p1: (0.0).into(),
        p2: (1.1).into(),
        p3: (2.2).into(),
    };

    Triangle::new + From<(i32, i32)> for Point
    let triangle = Triangle::new(
        (0.0).into(),
        (1.1).into(),
        (2.2).into(),
    );

    From<[Into
      
       ; 3]> for Triangle
      
    let triangle: Triangle = [
        (0.0),
        (1.1),
        (2.2),
    ].into();
}

Copy the code

There is no mandatory rule as to when, how, and why you should implement From

for our type. It is up to you to judge the situation.

A common use of Into

is to make functions that need to have values universal, regardless of whether they own or borrow values.

struct Person {
    name: String,}impl Person {
    / / to accept:
    // - String
    fn new1(name: String) -> Person {
        Person { name }
    }

    / / to accept:
    // - String
    // - &String
    // - &str
    // - Box<str>
    // - Cow<'_, str>
    // - char
    // Because all of the above types can be cast to String
    fn new2<N: Into<String>>(name: N) -> Person {
        Person { name: name.into() }
    }
}

Copy the code

Error Handling

The best time to talk about Error handling and Error traits is right after Display, Debug, Any, and From, but before TryFrom, which is why the Error handling part is awkwardly embedded in between conversion traits.

Error

trait Error: Debug + Display {
    // Provide a default implementation
    fn source(&self) - >Option"The & (dyn Error + 'static) >;fn backtrace(&self) - >Option<&Backtrace>;
    fn description(&self) - > &str;
    fn cause(&self) - >OptionThe < &dyn Error>;
}
Copy the code

In Rust, errors are returned, not thrown. Let’s look at an example.

If we wanted to make our program safer, we could implement a safe_div function that would return a Result like this:

use std::fmt;
use std::error;

#[derive(Debug, PartialEq)]
struct DivByZero;

impl fmt::Display for DivByZero {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "division by zero error")}}impl error::Error for DivByZero {}

fn safe_div(numerator: i32, denominator: i32) - >Result<i32, DivByZero> {
    if denominator == 0 {
        return Err(DivByZero);
    }
    Ok(numerator / denominator)
}

#[test] / / ✅
fn test_safe_div() {
    assert_eq!(safe_div(8.2), Ok(4));
    assert_eq!(safe_div(5.0), Err(DivByZero));
}

Copy the code

Because errors are returned rather than thrown, they must be handled explicitly, and if the current function is unable to handle an error, the function should pass the error to its caller. The most common way to pass an error is to use? Operator, which is the now deprecated try! Macro syntax sugar:

macro_rules! try {
    ($expr:expr) => {
        match $expr {
            // if Ok just unwrap the value
            Ok(val) => val,
            // if Err map the err value using From and return
            Err(err) => {
                return Err(From::from(err)); }}}; }Copy the code

If we wanted to write a function that reads the contents of a file into a String, we could write something like this:

use std::io::Read;
use std::path::Path;
use std::io;
use std::fs::File;

fn read_file_to_string(path: &Path) -> Result<String, io::Error> {
    let mutfile = File::open(path)? ;/ / ⬆ ️ IO: : Error
    let mut contents = String::new();
    file.read_to_string(&mutcontents)? ;/ / ⬆ ️ IO: : Error
    Ok(contents)
}
Copy the code

Assuming that the contents of the file we are currently reading are a string of numbers, and we want to sum these numbers, we might update the function to look like this:

use std::io::Read;
use std::path::Path;
use std::io;
use std::fs::File;

fn sum_file(path: &Path) -> Result<i32./* What to put here? * /> {
    let mutfile = File::open(path)? ;/ / ⬆ ️ IO: : Error
    let mut contents = String::new();
    file.read_to_string(&mutcontents)? ;/ / ⬆ ️ IO: : Error
    let mut sum = 0;
    for line in contents.lines() {
        sum += line.parse::<i32> ()? ;/ / ⬆ ️ ParseIntError
    }
    Ok(sum)
}
Copy the code

But now, what should be the error type in our Result? It returns either an IO ::Error or a ParseIntError. We tried to find a third way to solve this problem, the fastest and messiest way to start and the most robust way to end.

The first way is to identify all types that implement Error and Display, so we map all errors to String and use String as our Error type:

use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;

fn sum_file(path: &Path) -> Result<i32.String> {
    let mutfile = File::open(path) .map_err(|e| e.to_string())? ;// ⬆️ IO ::Error -> String
    let mut contents = String::new();
    file.read_to_string(&mutcontents) .map_err(|e| e.to_string())? ;// ⬆️ IO ::Error -> String
    let mut sum = 0;
    for line in contents.lines() {
        sum += line.parse::<i32>() .map_err(|e| e.to_string())? ;// ⬆️ ParseIntError -> String
    }
    Ok(sum)
}
Copy the code

The downside of this approach, however, is that we throw away all the error type information, making it difficult for the caller to handle the error.

A less obvious advantage is that we can customize the string to provide more context-specific information. For example, ParseIntError usually becomes the string “invalid Digit found in string”, the information is very vague and does not mention what the invalid string is or what type of integer it is trying to resolve. This error message is almost useless if we are debugging the problem. However, there is something we can do to improve the problem by providing all the contextual information ourselves:

sum += line.parse::<i32>()
    .map_err(|_| format!("failed to parse {} into i32", line))? ;Copy the code

The second is to take full advantage of the generic Blanket Impl in the library:

impl<E: error::Error> From<E> for Box<dyn error::Error>;
Copy the code

That means that any Error type can pass, right? Is implicitly converted to Box

, so we can set the error type in the Result returned by any function that might produce an error to Box

. The operator will do the rest of the work:

use std::fs::File;
use std::io::Read;
use std::path::Path;
use std::error;

fn sum_file(path: &Path) -> Result<i32.Box<dyn error::Error>> {
    let mutfile = File::open(path)? ;// ⬆️ IO ::Error -> Box
      
    let mut contents = String::new();
    file.read_to_string(&mutcontents)? ;// ⬆️ IO ::Error -> Box
      
    let mut sum = 0;
    for line in contents.lines() {
        sum += line.parse::<i32> ()? ;// ⬆️ ParseIntError -> Box
      
    }
    Ok(sum)
}

Copy the code

Although more concise, it seems to have the disadvantage of the previous approach, which is the loss of type information. In most cases this is true, but if callers know the implementation details of the function, they can still handle different error types by using the downcast_ref() method on error:: error, just as it does on dyn Any.

fn handle_sum_file_errors(path: &Path) {
    match sum_file(path) {
        Ok(sum) => println!("the sum is {}", sum),
        Err(err) => {
            if let Some(e) = err.downcast_ref::<io::Error>() {
                / / processing IO: : Error
            } else if let Some(e) = err.downcast_ref::<ParseIntError>() {
                / / ParseIntError processing
            } else {
                // We know that sum_file returns only one of the above errors
                // So it won't reach this branch
                unreachable!(a); }}}}Copy the code

The third approach, which is the most robust and type-safe approach, can summarize these different errors and build our own custom error types using an enumerated type:

use std::num::ParseIntError;
use std::fs::File;
use std::io;
use std::io::Read;
use std::path::Path;
use std::error;
use std::fmt;

#[derive(Debug)]
enum SumFileError {
    Io(io::Error),
    Parse(ParseIntError),
}

impl From<io::Error> for SumFileError {
    fn from(err: io::Error) -> Self {
        SumFileError::Io(err)
    }
}

impl From<ParseIntError> for SumFileError {
    fn from(err: ParseIntError) -> Self {
        SumFileError::Parse(err)
    }
}

impl fmt::Display for SumFileError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            SumFileError::Io(err) => write!(f, "sum file error: {}", err),
            SumFileError::Parse(err) => write!(f, "sum file error: {}", err),
        }
    }
}

impl error::Error for SumFileError {
    // The default implementation of this method always returns None
    // But let's rewrite it now to make it more useful
    fn source(&self) - >Option"The & (dyn error::Error + 'static) > {Some(match self {
            SumFileError::Io(err) => err,
            SumFileError::Parse(err) => err,
        })
    }
}

fn sum_file(path: &Path) -> Result<i32, SumFileError> {
    let mutfile = File::open(path)? ;// ⬆️ IO ::Error -> SumFileError
    let mut contents = String::new();
    file.read_to_string(&mutcontents)? ;// ⬆️ IO ::Error -> SumFileError
    let mut sum = 0;
    for line in contents.lines() {
        sum += line.parse::<i32> ()? ;// ⬆️ ParseIntError -> SumFileError
    }
    Ok(sum)
}

fn handle_sum_file_errors(path: &Path) {
    match sum_file(path) {
        Ok(sum) => println!("the sum is {}", sum),
        Err(SumFileError::Io(err)) => {
            / / processing IO: : Error
        },
        Err(SumFileError::Parse(err)) => {
            / / ParseIntError processing}}},Copy the code

Conversion Traits Continued type

TryFrom & TryInto

TryFrom and TryInto are potentially failing versions of From and Into.

trait TryFrom<T> {
    type Error;
    fn try_from(value: T) -> Result<Self, Self::Error>;
}

trait TryInto<T> {
    type Error;
    fn try_into(self) - >Result<T, Self::Error>;
}

Copy the code

Like Into, we cannot implement TryInto because its implementation is provided by the Generic blanket IMPl:

impl<T, U> TryInto<U> for T
where
    U: TryFrom<T>,
{
    type Error = U::Error;

    fn try_into(self) - >Result<U, U::Error> {
        U::try_from(self)}}Copy the code

Given the context of our program, it doesn’t make sense for x and y in Point to have values less than -1000 or greater than 1000. Here we use TryFrom to override the previous From implementation to tell the user that the conversion can now fail.

use std::convert::TryFrom;
use std::error;
use std::fmt;

struct Point {
    x: i32,
    y: i32,}#[derive(Debug)]
struct OutOfBounds;

impl fmt::Display for OutOfBounds {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "out of bounds")}}impl error::Error for OutOfBounds {}

// Now it is possible to make an error
impl TryFrom<(i32.i32) >for Point {
    type Error = OutOfBounds;
    fn try_from((x, y): (i32.i32)) - >Result<Point, OutOfBounds> {
        if x.abs() > 1000 || y.abs() > 1000 {
            return Err(OutOfBounds);
        }
        Ok(Point { x, y })
    }
}

// Still no mistake
impl From<Point> for (i32.i32) {
    fn from(Point { x, y }: Point) -> Self {
        (x, y)
    }
}

Copy the code

TryFrom<[TryInto ; 3]> for Triangle:

use std::convert::{TryFrom, TryInto};
use std::error;
use std::fmt;

struct Point {
    x: i32,
    y: i32,}#[derive(Debug)]
struct OutOfBounds;

impl fmt::Display for OutOfBounds {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "out of bounds")}}impl error::Error for OutOfBounds {}

impl TryFrom<(i32.i32) >for Point {
    type Error = OutOfBounds;
    fn try_from((x, y): (i32.i32)) - >Result<Self, Self::Error> {
        if x.abs() > 1000 || y.abs() > 1000 {
            return Err(OutOfBounds);
        }
        Ok(Point { x, y })
    }
}

struct Triangle {
    p1: Point,
    p2: Point,
    p3: Point,
}

impl<P> TryFrom<[P; 3] >for Triangle
where
    P: TryInto<Point>,
{
    type Error = P::Error;
    fn try_from([p1, p2, p3]: [P; 3]) -> Result<Self, Self::Error> {
        Ok(Triangle { p1: p1.try_into()? , p2: p2.try_into()? , p3: p3.try_into()? ,}}}fn example() - >Result<Triangle, OutOfBounds> {
    let t: Triangle = [(0.0), (1.1), (2.2)].try_into()? ;Ok(t)
}

Copy the code

FromStr

trait FromStr {
    type Err;
    fn from_str(s: &str) - >Result<Self, Self::Err>;
}
Copy the code

The FromStr type allows a failable conversion from &str to Self. The most common use is to call the parse() method on &str:

use std::str::FromStr;

fn example<T: FromStr>(s: &'static str) {
    // These are all equal
    let t: Result<T, _> = FromStr::from_str(s);
    let t = T::from_str(s);
    let t: Result<T, _> = s.parse();
    let t = s.parse::<T>(); // The most common
}
Copy the code

For example, the implementation at Point:

use std::error;
use std::fmt;
use std::iter::Enumerate;
use std::num::ParseIntError;
use std::str::{Chars, FromStr};

#[derive(Debug, Eq, PartialEq)]
struct Point {
    x: i32,
    y: i32,}impl Point {
    fn new(x: i32, y: i32) - >Self {
        Point { x, y }
    }
}

#[derive(Debug, PartialEq)]
struct ParsePointError;

impl fmt::Display for ParsePointError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "failed to parse point")}}impl From<ParseIntError> for ParsePointError {
    fn from(_e: ParseIntError) -> Self {
        ParsePointError
    }
}

impl error::Error for ParsePointError {}

impl FromStr for Point {
    type Err = ParsePointError;

    fn from_str(s: &str) - >Result<Self, Self::Err> {
        let is_num = |(_, c): &(usize.char)| matches! (c,'0'..='9' | The '-');
        letisnt_num = |t: &(_, _)| ! is_num(t);let get_num =
            |char_idxs: &mut Enumerate<Chars<'_>>| -> Result< (usize.usize), ParsePointError> {
                let(start, _) = char_idxs .skip_while(isnt_num) .next() .ok_or(ParsePointError)? ;let(end, _) = char_idxs .skip_while(is_num) .next() .ok_or(ParsePointError)? ;Ok((start, end))
            };

        let mut char_idxs = s.chars().enumerate();
        let (x_start, x_end) = get_num(&mutchar_idxs)? ;let (y_start, y_end) = get_num(&mutchar_idxs)? ;let x = s[x_start..x_end].parse::<i32> ()? ;let y = s[y_start..y_end].parse::<i32> ()? ;Ok(Point { x, y })
    }
}

#[test] / / ✅
fn pos_x_y() {
    let p = "(4, 5)".parse::<Point>();
    assert_eq!(p, Ok(Point::new(4.5)));
}

#[test] / / ✅
fn neg_x_y() {
    let p = "(- 6-2)".parse::<Point>();
    assert_eq!(p, Ok(Point::new(-6, -2)));
}

#[test] / / ✅
fn not_a_point() {
    let p = "not a point".parse::<Point>();
    assert_eq!(p, Err(ParsePointError));
}
Copy the code

FromStr and TryFrom<&str> have the same signature. As long as we implement one through the other, it doesn’t matter which we implement first. Here’s TryFrom<&str> for Point, assuming it already implements FromStr:

impl TryFrom<&str> for Point {
    type Error = <Point as FromStr>::Err;
    fn try_from(s: &str) - >Result<Point, Self::Error> {
        <Point as FromStr>::from_str(s)
    }
}

Copy the code

AsRef & AsMut

trait AsRef<T: ?Sized> {
    fn as_ref(&self) -> &T;
}

trait AsMut<T: ?Sized> {
    fn as_mut(&mut self) - > &mut T;
}

Copy the code

AsRef is used for lightweight reference-to-reference conversion. However, one of its most common uses is to make functions generic about whether or not to acquire ownership:

/ / to accept:
// - &str
// - &String
fn takes_str(s: &str) {
    // use &str
}

/ / to accept:
// - &str
// - &String
// - String
fn takes_asref_str<S: AsRef<str>>(s: S) {
    let s: &str = s.as_ref();
    / / use & STR
}

fn example(slice: &str, borrow: &String, owned: String) {
    takes_str(slice);
    takes_str(borrow);
    takes_str(owned); / / ❌
    takes_asref_str(slice);
    takes_asref_str(borrow);
    takes_asref_str(owned); / / ✅
}

Copy the code

Another common use is to return a reference to an internal private data wrapped in a protected invariance type. A good example in the library is String, which wraps around Vec

:

struct String {
    vec: Vec<u8>,}Copy the code

The internal Vec

cannot be exposed, because if it did, people would modify the bytes inside and break the valid UTF-8 encoding in the String. However, it is safe to expose an immutable read-only reference to an internal byte array, as follows:

impl AsRef"[u8] >for String;
Copy the code

In general, it makes sense to implement AsRef for a type only if it wraps around other types to provide additional functionality for the inner type or to protect the invariance of the inner type. Let’s look at an inappropriate use of AsRef:

struct User {
    name: String,
    age: u32,}impl AsRef<String> for User {
    fn as_ref(&self) - > &String{&self.name
    }
}

impl AsRef<u32> for User {
    fn as_ref(&self) - > &u32{&self.age
    }
}

Copy the code

This worked at first, and seemed to make sense, but as we added more members to User, the problem arose:

struct User {
    name: String,
    email: String,
    age: u32,
    height: u32,}impl AsRef<String> for User {
    fn as_ref(&self) - > &String{,// Return name or email?}}impl AsRef<u32> for User {
    fn as_ref(&self) - > &u32 {
        // Do we return age or height?}}Copy the code

User is made up of a String and u32, but it’s not the same as a String and a u32, and we might even have more types:

struct User {
    name: Name,
    email: Email,
    age: Age,
    height: Height,
}
Copy the code

It makes little sense to implement AsRef for such a type, because AsRef is used for reference-to-reference conversions between semantically equivalent things, and Name, Email, Age, and Height are not equivalent to a User.

Here is a good example, where we introduce a new type of Moderator, which wraps just one User and adds specific auditing permissions:

struct User {
    name: String,
    age: u32,}// Unfortunately, the standard library does not provide a generic blanket IMPl to avoid this duplication of implementations
impl AsRef<User> for User {
    fn as_ref(&self) -> &User {
        self}}enum Privilege {
    BanUsers,
    EditPosts,
    DeletePosts,
}

// Although Moderators have some special permissions, they are still normal users
// And should do the same
struct Moderator {
    user: User,
    privileges: Vec<Privilege>
}

impl AsRef<Moderator> for Moderator {
    fn as_ref(&self) -> &Moderator {
        self}}impl AsRef<User> for Moderator {
    fn as_ref(&self) -> &User {
        &self.user
    }
}

// Both User and Moderators (also a type of User) should be callable
fn create_post<U: AsRef<User>>(u: U) {
    let user = u.as_ref();
    // etc
}

fn example(user: User, moderator: Moderator) {
    create_post(&user);
    create_post(&moderator); / / ✅
}

Copy the code

This is effective because Moderator is the User. Here’s an example from the Deref section, which we implemented using AsRef:

use std::convert::AsRef;

struct Human {
    health_points: u32,}impl AsRef<Human> for Human {
    fn as_ref(&self) -> &Human {
        self}}enum Weapon {
    Spear,
    Axe,
    Sword,
}

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

impl AsRef<Soldier> for Soldier {
    fn as_ref(&self) -> &Soldier {
        self}}impl AsRef<Human> for Soldier {
    fn as_ref(&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 AsRef<Knight> for Knight {
    fn as_ref(&self) -> &Knight {
        self}}impl AsRef<Soldier> for Knight {
    fn as_ref(&self) -> &Soldier {
        &self.soldier
    }
}

impl AsRef<Human> for Knight {
    fn as_ref(&self) -> &Human {
        &self.soldier.human
    }
}

enum Spell {
    MagicMissile,
    FireBolt,
    ThornWhip,
}

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

impl AsRef<Mage> for Mage {
    fn as_ref(&self) -> &Mage {
        self}}impl AsRef<Human> for Mage {
    fn as_ref(&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 AsRef<Wizard> for Wizard {
    fn as_ref(&self) -> &Wizard {
        self}}impl AsRef<Mage> for Wizard {
    fn as_ref(&self) -> &Mage {
        &self.mage
    }
}

impl AsRef<Human> for Wizard {
    fn as_ref(&self) -> &Human {
        &self.mage.human
    }
}

fn borrows_human<H: AsRef<Human>>(human: H) {}
fn borrows_soldier<S: AsRef<Soldier>>(soldier: S) {}
fn borrows_knight<K: AsRef<Knight>>(knight: K) {}
fn borrows_mage<M: AsRef<Mage>>(mage: M) {}
fn borrows_wizard<W: AsRef<Wizard>>(wizard: W) {}

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

Deref didn’t work in the previous example because dereferencing casts are implicit conversions between types, which leaves room for people to develop wrong ideas and expectations about how they should behave. AsRef works because it makes conversions between types explicit and leaves no room for developers’ wrong ideas and expectations.

Borrow & BorrowMut

trait Borrow<Borrowed>
where
    Borrowed: ?Sized,
{
    fn borrow(&self) -> &Borrowed;
}

trait BorrowMut<Borrowed>: Borrow<Borrowed>
where
    Borrowed: ?Sized,
{
    fn borrow_mut(&mut self) - > &mut Borrowed;
}

Copy the code

These traits were invented to solve the very specific problem of finding keys of type String in HashSet, HashMap, BTreeSet, and BTreeMap using values of type &str.

We can think of Borrow

and BorrowMut

as the more stringent AsRef

and AsMut

that return Eq, Hash, and Ord implementations of references &t that are equivalent to Self. It’s easier to understand with the following example:

use std::borrow::Borrow;
use std::hash::Hasher;
use std::collections::hash_map::DefaultHasher;
use std::hash::Hash;

fn get_hash<T: Hash>(t: T) -> u64 {
    let mut hasher = DefaultHasher::new();
    t.hash(&mut hasher);
    hasher.finish()
}

fn asref_example<Owned, Ref>(owned1: Owned, owned2: Owned)
where
    Owned: Eq + Ord + Hash + AsRef<Ref>,
    Ref: Eq + Ord + Hash
{
    let ref1: &Ref = owned1.as_ref();
    let ref2: &Ref = owned2.as_ref();

    // refs aren't required to be equal if owned types are equal
    assert_eq!(owned1 == owned2, ref1 == ref2); / / ❌

    let owned1_hash = get_hash(&owned1);
    let owned2_hash = get_hash(&owned2);
    let ref1_hash = get_hash(&ref1);
    let ref2_hash = get_hash(&ref2);

    // ref hashes aren't required to be equal if owned type hashes are equal
    assert_eq!(owned1_hash == owned2_hash, ref1_hash == ref2_hash); / / ❌

    // ref comparisons aren't required to match owned type comparisons
    assert_eq!(owned1.cmp(&owned2), ref1.cmp(&ref2)); / / ❌
}

fn borrow_example<Owned, Borrowed>(owned1: Owned, owned2: Owned)
where
    Owned: Eq + Ord + Hash + Borrow<Borrowed>,
    Borrowed: Eq + Ord + Hash
{
    let borrow1: &Borrowed = owned1.borrow();
    let borrow2: &Borrowed = owned2.borrow();

    // borrows are required to be equal if owned types are equal
    assert_eq!(owned1 == owned2, borrow1 == borrow2); / / ✅

    let owned1_hash = get_hash(&owned1);
    let owned2_hash = get_hash(&owned2);
    let borrow1_hash = get_hash(&borrow1);
    let borrow2_hash = get_hash(&borrow2);

    // borrow hashes are required to be equal if owned type hashes are equal
    assert_eq!(owned1_hash == owned2_hash, borrow1_hash == borrow2_hash); / / ✅

    // borrow comparisons are required to match owned type comparisons
    assert_eq!(owned1.cmp(&owned2), borrow1.cmp(&borrow2)); / / ✅
}

Copy the code

Being aware of these traits and why they exist is useful because it helps to figure out some of the methods of HashSet, HashMap, BTreeSet, and BTreeMap, but we rarely need to implement these traits for our types because we rarely need to create a pair of types. One is a borrowed version of the other. If we have a certain type T, &t will do the job 99.99% of the time, and because the generic blanket IMPl, T:Borrorw

is already implemented for all types T, So we don’t need to implement it manually and we don’t need to create a U for T:Borrow
.

ToOwned

trait ToOwned {
    type Owned: Borrow<Self>;
    fn to_owned(&self) -> Self::Owned;

    // Provide a default implementation
    fn clone_into(&self, target: &mut Self::Owned);
}
Copy the code

ToOwned is a more generic version of Clone. Clone allows us to take an &T and turn it into a T, but ToOwned allows us to take an & and turn it into a Owned, where Owned: Borrow

.

In other words, we cannot clone a String from an &str, or a PathBuf from an &PATH, or an OsString from an &OSstr, because the Clone method signature does not support such cross-type cloning, which is where ToOwned comes in.

Like Borrow and BorrowMut, it’s also beneficial to know the trait and understand what it is, except that we rarely need to implement it for our types.