the AsRef trait is everywhere in the std lib and very handy. However, the benefit using it is maybe not too obvious. But read on, and you will see some useful examples and where to apply it.

This is the 2nd post on the series "practical rust bites" that shows very tiny pieces of rust taken out of practical real projects. It aims to illustrate only one rust idiom or concept at a time with very practical examples.

Background

AsRef can be found in the std::convert module and is used for cheap reference-to-reference conversion.

It's a very essential trait widely used in the std library and helps with seamless reference conversions from one type to another. Often it is at play, and you do not even realize it is there.

Example: String or &str

Did you ever have had a function where you wanted to accept a string as a parameter? You might then also have asked yourself should you accept a string reference (as in &str) or an owned string (as in String)?

So you would have something like this in mind:

fn take_a_str(some: &str) {}

and the other one:

fn take_a_string(some: String) {}

So why not have both?!

At this very point AsRef<str> comes in very handy, because both types str and String implement this trait.

fn take_a_str(some: impl AsRef<str>) {
    let some = some.as_ref();
    println!("{some}");
}

fn main() {
    take_a_str("str");
    take_a_str("String".to_string());
    
    // also `&String` is supported:
    let string_ref = "StringRef".to_string();
    take_a_str(&string_ref);
}

check out the code on the playground

As you can see this version of take_a_str accepts a type that just implements AsRef<str>.

By doing so some: impl AsRef<str> does not make any concrete assumptions on the type that is provided. Instead, it just insists that any given type only implements this trait.

Example: wrapper type

In this example we want to focus on how can you implement AsRef yourself for any arbitrary struct.

Let's look at this tiny program:

pub struct Envelope {
    letter: String
}

fn main() {
    let a_letter = Envelope { 
        letter: "a poem".to_string() 
    };

    println!("this is a letter: {}", &a_letter);
}

But of course you'd say now, well, it does not implement Display and so does the rust compiler agree with you:

error[E0277]: `Envelope` doesn't implement `std::fmt::Display`
  --> src/main.rs:10:38
   |
10 |     println!("this is a letter: {}", &a_letter);
   |                                      ^^^^^^^^^ `Envelope` cannot be formatted with the default formatter
   |
   = help: the trait `std::fmt::Display` is not implemented for `Envelope`
   = note: in format strings you may be able to use `{:?}` (or {:#?} for pretty-print) instead
   = note: this error originates in the macro `$crate::format_args_nl` (in Nightly builds, run with -Z macro-backtrace for more info)

For more information about this error, try `rustc --explain E0277`.

But we ignore Display for now and focus on another use case. That is we want to access the inner value of Envelope.

Solving this problem with implementing AsRef<str> we would do the following:

impl AsRef<str> for Envelope {
    fn as_ref(&self) -> &str {
        // here we up-call to the `AsRef<str>` implementation for String
        self.letter.as_ref()
    }
}

and with this we need just one little adjustment:

fn main() {
    let a_letter = Envelope {
        letter: "a poem".to_string()
    };

    println!("this is a letter: {}", a_letter.as_ref());
}

Now the rust compiler does not complain anymore.

Again, of course it would be appropriate to implement Display for this very println! case, but the point is here that we would want to access the inner data as reference.

check out the full example on the playground

Example: a composed type

I have to admit, take this example with a grain of salt. It's very likely that this kind of usage would lead to problems with bigger structs. So as a rule of thumb if a struct has more than 2 fields, better not go down this path.

let's look at a simple struct that represents a weight:

struct Weight {
    weight: f32,
    unit: String
}

impl Weight {
    /// Weight in Tons that is 157.47 stones 
    pub fn from_tons(weight: f32) -> Self {
        Self { weight, unit: "t".to_string() }
    }

    /// Weight in Stones
    pub fn from_stones(weight: f32) -> Self {
        Self { weight, unit: "st".to_string() }
    }
}

As you can see we have not given pub fields and also no getter accessor functions.

So how we can actually get our hand on the data inside?

You've guessed it, AsRef is our friend here as well.

impl AsRef<str> for Weight {
    fn as_ref(&self) -> &str {
        &self.unit
    }
}

impl AsRef<f32> for Weight {
    fn as_ref(&self) -> &f32 {
        &self.weight
    }
}

So here we use the AsRef trait for the types f32 and str to get access to the weight and unit inside the struct.

fn main() {
    let a_ton = Weight::from_tons(1.3);

    let tons: &f32 = a_ton.as_ref();
    let unit: &str = a_ton.as_ref();

    println!("a weight of {tons} {unit}");
}

you could also skip the variable bindings and make it less verbose:

fn main() {
    let a_ton = Weight::from_tons(1.3);

    println!(
        "a weight of {} {}",
        a_ton.as_ref() as &f32,
        a_ton.as_ref() as &str
    );
}

checkout the full example on the playground

Wrap up

AsRef is a very handy tool and if you go through the standard library, you will find it implemented for a lot of types. By this a lot of reference types become interchangeable and this increases the overall ergonomics a lot.

As you also have seen how AsRef can be useful to get access to inner data of structs without having to provide accessory methods or public fields.

A very related function to get the inner value of a struct, but as value (not as reference) is the .into_inner(). As you can see it is used a lot in the std lib, but it is not bound to a trait. So this is more of a convention: Whenever you need to consume a struct and unfold the inner wrapped type, then .into_inner() is the common schema to go for.

Versions used for this post:

$ cargo --version && rustc --version
cargo 1.61.0 (a028ae42f 2022-04-29)
rustc 1.61.0 (fe5b13d68 2022-05-18)

To receive updates about new blog post and other rust related news you can follow me on twitter.