Borrowed Types
Recipe | Crates |
---|---|
Use the Borrow Trait to Work with Borrowed Types Synonymous with Owned Types | |
Differences between Borrow , Deref , and AsRef in Generic Code | |
Implement Borrow for a Custom Type |
Use the Borrow
Trait to Work with Borrowed Types Synonymous with Owned Types
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.