LPireyn

Borrow vs. AsRef

A short explanation of the difference between the Borrow and the AsRef traits in Rust.

Published on . Reading time: 2 minutes.

Tagged with #rust.

The Borrow and AsRef traits in Rust are very similar. Both have an associated type and a single required method with the same signature. The difference between these traits lies entirely in their contract.

Although both traits are still widely seen as equivalent, they are not. See for example this StackOverflow answer.

Whereas a reference borrowed via the Borrow trait must have the same semantics as the value itself, a reference borrowed via the AsRef trait does not have any such requirements.

Example

A case-insensitive string can be implemented with the newtype pattern on a String:

#[derive(Clone, Debug)]
pub struct CIString(String);

In order to make CIString case-insensitive, we need to implement the PartialEq and Eq traits, as well as the PartialCmp and Cmp traits. Furthermore, we will likely want to implement the Hash trait, which also depends on the case-insensitive semantics. For brevity, I do not even mention other common methods and traits implementations – such as Display.

It is a good idea to provide an as_str method, and to implement the AsRef<str> trait accordingly:

impl CIString {
    #[inline]
    pub fn as_str(&self) -> &str {
        self.0.as_str()
    }
}

impl AsRef<str> for CIString {
    #[inline]
    fn as_ref(&self) -> &str {
        self.as_str()
    }
}

This allows borrowing a case-insensitive string as a &str. The borrowed reference will not be case-insensitive, but that is expected. The AsRef trait is in the std::convert module after all.

However, we must not implement the Borrow<str> trait. Indeed, the contract of the Borrow trait says that the borrowed reference must have the same semantics as the value itself, which in our case is that it is case-insensitive.