Borrow

Borrow Trait

std

Rust data types often have multiple representations to suit different needs. For example, smart pointer types like Box<T> or Arc<T> allow you to choose how a value is stored and managed. Some types, such as String, extend a more basic type (str) by adding features like mutability and dynamic growth, which require extra metadata. This design lets you use lightweight, immutable, borrowed types when possible, and switch to more flexible, feature-rich, memory-owning types when necessary. Common type pairs include:

Borrowed TypeOwned Type
&strString
&CStrCString
&OsStrOsString
&PathPathBuf
&[T]Vec<T>
&[T][T; N]
&TBox<T>
&TArc<T>

Types express that they can be borrowed as some type T by implementing Borrow<T>. Use the trait's borrow method to return a reference &T. For instance, a Box<T> can be borrowed as &T, while a String can be borrowed as &str.

A type is free to borrow as several different types.

If a type wishes to mutably borrow as another type, allowing the underlying data to be modified, it can additionally implement the BorrowMut trait.

With Borrow<T> and BorrowMut<T>, it is possible to write generic code that accept &T, and therefore works with both such owned and borrowed data. It is a form of trait-based polymorphism, which enables flexible APIs that accept multiple forms of a type.

Borrow is particularly useful when you are using (or implementing) a data structure, and you want to use either an owned or borrowed type as synonymous.

For example, as a data collection, HashMap<K, V> owns both keys and values. If the key's actual data is wrapped in a managing type of some kind, it should, however, still be possible to search for a value using a reference to the key's data. For instance, if the key is a string, then it is likely stored with the hash map as a String, while it should be possible to search using a &str. The Borrow trait enables this: you can insert with a String, but retrieve values using a &str reference, allowing for flexible and efficient key access without unnecessary allocations or conversions. Specifically, HashMap<K, V> functions like get accept &Q where K: Borrow<Q> and String is Borrow<str>:

use std::collections::HashMap;

// This is the most common use of `Borrow`.
// A `HashMap<String, V>` can be queried with a `&str`,
// because `String: Borrow<str>`.
fn main() {
    let mut scores: HashMap<String, u32> = HashMap::new();
    scores.insert("alice".to_string(), 10);
    scores.insert("bob".to_string(), 20);

    // Lookup with `&str` - no need to allocate a `String`:
    assert_eq!(scores.get("alice"), Some(&10));
    assert_eq!(scores.get("bob"), Some(&20));
}

You can, of course, write a generic function that accepts any type that can borrow as e.g. a string slice, be it String, &String, or &str, and use it in lookups seamlessly:

use std::borrow::Borrow;
use std::collections::HashMap;

fn find_score<K>(map: &HashMap<String, u32>, key: K) -> Option<u32>
where
    K: Borrow<str>,
{
    map.get(key.borrow()).copied()
}

fn main() {
    let mut scores = HashMap::new();
    scores.insert("eve".to_string(), 30);

    // Pass a `&str`.
    assert_eq!(find_score(&scores, "eve"), Some(30));

    // Pass a `String`.
    let name = "eve".to_string();
    assert_eq!(find_score(&scores, name), Some(30));
}

Implement Borrow for a Custom Type

std

You can implement Borrow on your own types.

This said, BEWARE: Borrow is different from AsRef<T> in that Borrow is intended for equivalence - meaning the borrowed value should behave identically to the owned one. In particular, Eq, Ord and Hash must be equivalent for borrowed and owned values: x.borrow() == y.borrow() should give the same result as x == y:

mod cis {
    use std::borrow::Borrow;
    use std::convert::From;
    use std::hash::Hash;

    // Case-insensitive string.
    // Implement `Eq`, `PartialEq`, `Hash` (used by `HashMap`).
    #[derive(Debug, PartialEq, Eq, Hash)]
    pub struct CaseInsensitiveString(String);

    impl From<String> for CaseInsensitiveString {
        fn from(s: String) -> Self {
            // We store the lowercase version of the string.
            Self(s.to_ascii_lowercase())
        }
    }

    // Equality with `str`, both ways:
    impl PartialEq<str> for CaseInsensitiveString {
        fn eq(&self, other: &str) -> bool {
            self.0.eq_ignore_ascii_case(other)
        }
    }

    impl PartialEq<CaseInsensitiveString> for str {
        fn eq(&self, other: &CaseInsensitiveString) -> bool {
            self.eq_ignore_ascii_case(&other.0)
        }
    }

    // Implement `Borrow<str>` to allow lookup.
    impl Borrow<str> for CaseInsensitiveString {
        fn borrow(&self) -> &str {
            &self.0
        }
    }
}

fn calculate_hash<T: std::hash::Hash>(t: &T) -> u64 {
    use std::hash::Hasher;
    let mut s = std::hash::DefaultHasher::new();
    t.hash(&mut s);
    s.finish()
}

fn main() {
    use std::borrow::Borrow;
    use std::collections::HashMap;

    use cis::CaseInsensitiveString;

    let x = CaseInsensitiveString::from("Hello".to_string());
    let y = CaseInsensitiveString::from("HELLO".to_string());

    assert!(x == *"heLLo");
    assert!(*"HellO" == y);

    // The `Borrow`-required invariants must hold:
    let xb: &str = x.borrow();
    let yb: &str = y.borrow();

    // - Equality holds:
    assert!(x == y);
    assert_eq!(xb, yb);

    // - Hashes are the same:
    assert_eq!(calculate_hash(&x), calculate_hash(&y));
    assert_eq!(calculate_hash(&xb), calculate_hash(&yb));

    // Conversely:
    let z = CaseInsensitiveString::from("World".to_string());
    let zb: &str = z.borrow();
    assert!(x != z);
    assert_ne!(calculate_hash(&x), calculate_hash(&z));
    assert!(xb != zb);
    assert_ne!(calculate_hash(&xb), calculate_hash(&zb));

    // Use the new `CaseInsensitiveString` type:
    let mut map: HashMap<CaseInsensitiveString, usize> = HashMap::new();
    map.insert(CaseInsensitiveString::from("HelLO".to_string()), 1);

    // Lookup with a (lowercase) `&str` thanks to `Borrow<str>`.
    if let Some(value) = map.get("hello") {
        println!("Found: {value}");
    } else {
        panic!("Not found.");
    }
}

Differences between Borrow, Deref, and AsRef in Generic Code

The Borrow and AsRef traits are very similar, but different in purposes.

  • Use Borrow when you want to abstract over different kinds of borrowing, or when you're building a data structure that treats owned and borrowed values in equivalent ways, such as hashing and comparison.
  • Use AsRef when you want to convert something to a reference directly or you're writing generic code.
  • Deref should be implemented by smart pointers only.

See AsRef, Conversion Traits and Smart Pointers for more details.

  • AsRef.
  • Conversion Traits.
  • Ownership and Borrowing.