AsRef and &T
Recipe | Crates |
---|---|
Accept Arguments of Multiple Types with AsRef | |
Use as_ref to Get a Reference to the Contained Value of a Smart Pointer | |
AsRef vs. Deref vs. Borrow |
Accept Arguments of Multiple Types with AsRef
The AsRef
⮳ trait is used for cheap reference-to-reference conversions (without allocating new memory). It provides a way to convert an object into a reference to another type.
The primary use case for AsRef<T>
is generic programming, especially for function arguments, to provide ergonomics and flexibility to the caller. In other words, this trait is often used to allow functions to accept arguments in multiple forms.
For example, Path
, PathBuf
, str
, String
, OsString
, OsStr
, Cow<'_, OsStr>
... all implement AsRef<Path>
. The std::path
standard library module therefore contains many functions that accepts AsRef<Path>
and therefore any of the aforementioned types as a argument. Other common implementations include AsRef<str>
, AsRef<OsStr>
, AsRef<[u8]>
, and AsRef<[T]>
:
/// This function takes a generic type `T` that implements the `AsRef<str>` /// trait. It converts the input `s` to a string slice (`&str`) using /// `as_ref()`. /// /// `s` is a value of type `T` that can be converted to a string slice. fn print_length<T: AsRef<str>>(s: T) { // The first line of a function accepting an `AsRef` is usually a call to // `as_ref`. let s_ref: &str = s.as_ref(); // Print the string slice and its length. println!("The length of '{}' is {}", s_ref, s_ref.len()); } /// Let's call `print_length` with arguments of various types. fn string_slice() { let string = String::from("Hello, world!"); let str_slice: &str = "Hello, Rust!"; // Using `print_length` with a `String`, which implements `AsRef<str>`. print_length(string); // Using `print_length` with a `&str`. print_length(str_slice); } /// `AsRef<[T]>` can also be used with slices, arrays, and vectors, /// although it is more common to use `&[T]` than `AsRef<T>`. fn print_vec<T: AsRef<[i32]>>(input: T) { let input = input.as_ref(); // Convert to a slice. println!("{:?}", input); } // `Vec<T>` and `[T; N]` implement `AsRef<[T]>`. // You can also pass a reference to a vector or array, thanks to a blanket // `impl AsRef<U> for &T where T: AsRef<U>` in the standard library. fn vec_example() { let vec: Vec<i32> = vec![1, 2, 3]; print_vec(&vec); print_vec(vec); let arr: [i32; 3] = [1, 2, 3]; print_vec(arr); let arr_ref: &[i32; 3] = &[1, 2, 3]; print_vec(arr_ref); let sli: &[i32] = &[1, 2, 3]; print_vec(sli); } fn main() { string_slice(); vec_example(); }
Use as_ref
to Get a Reference to the Contained Value of a Smart Pointer
For smart pointers like Box<T>
, Rc<T>
, Arc<T>
, etc., their as_ref()
methods typically provide a &T
, which is a reference to the contained value. This is distinct from simply using &
on the smart pointer itself, which would give you a reference to the smart pointer (&Box<T>
), not its contents.
use std::boxed::Box; /// Demonstrates the difference between taking a reference to a `Box` and using /// `as_ref()`. fn main() { let my_box: Box<String> = Box::new("hello from box".to_string()); // 1. Using `&my_box` - reference *to the Box* #[allow(clippy::borrowed_box)] let ref_to_box: &Box<String> = &my_box; println!("Debug of `ref_to_box`: {:?}", ref_to_box); // To access the inner string through `ref_to_box`, you'd need to // dereference twice: println!("Content via deref on `ref_to_box`: {}", **ref_to_box); // 2. Using `my_box.as_ref()` - reference *to the contained value* let ref_to_contained_string: &String = my_box.as_ref(); println!("Content via `as_ref()`: {}", ref_to_contained_string); println!( "Content as `&str` via `Deref`: {}", &**ref_to_contained_string ); }
Note that Option::as_ref()
and Result::as_ref()
are inherent methods on Option
and Result
respectively, not implementations of the std::convert::AsRef
trait. They transform an Option<T>
into an Option<&T>
(or Result<T, E>
into Result<&T, &E>
), which is useful for working with references inside these enums without consuming the original value.
// `Option::as_ref()` allows us to get an `Option<&String>`. // This lets us borrow the inner `String` if it exists, without moving it out of // `maybe_name`. fn process_optional_string(maybe_name: Option<String>) { let borrowed_name: Option<&String> = maybe_name.as_ref(); // If you used `maybe_name.unwrap()`, `maybe_name` would be consumed. // let consumed_name = maybe_name.unwrap(); if let Some(name_ref) = borrowed_name { println!("The name is: {}", name_ref); // We can still use `maybe_name` here, // because `as_ref()` didn't consume it. println!("Original option (still valid): {:?}", maybe_name); } else { println!("No name provided."); } // You can also get `Option<&str>` directly if the inner type is `String`. // `as_deref` is a shortcut for `opt.as_ref().map(|s| s.as_str())` or // equivalent `opt.as_ref().map(|s| &*s)`. let borrowed_str: Option<&str> = maybe_name.as_deref(); if let Some(name_str) = borrowed_str { println!("The name as &str: {}", name_str); } else { println!("Still no name as &str."); } } fn main() { process_optional_string(Some("Alice".to_string())); process_optional_string(None); }
AsRef
vs. Deref
vs. Borrow
These traits are related but have distinct purposes:
Deref
: Enables implicit coercions (e.g.,String
to&str
).AsRef
: Enables explicit (via.as_ref()
) cheap reference-to-reference conversions, often used in generic bounds for flexibility.Borrow
: Similar toAsRef
but imposes additional constraints (e.g.,Hash
,Eq
,Ord
must be equivalent for the borrowed value and the owned value). It's commonly used for keys in collections likeHashMap
.
For non-generic contexts or when Deref
coercion suffices, &T
remains the simpler and often preferred choice.
If you need to do a costly conversion, it is better to implement From
or write a custom function.
References
Related Topics
- Smart Pointers.
Note that AsMut
⮳ can be used for converting between mutable references.