Borrow
Recipe | Crates |
---|---|
Borrow Trait | |
Differences between Borrow , Deref , and AsRef in Generic Code | |
Implement Borrow for a Custom Type |
Borrow
Trait
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 Type | Owned Type |
---|---|
&str | String |
&CStr | CString |
&OsStr | OsString |
&Path | PathBuf |
&[T] | Vec<T> |
&[T] | [T; N] |
&T | Box<T> |
&T | Arc<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
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.
Related Topics
- AsRef.
- Conversion Traits.
- Ownership and Borrowing.