patternrustCritical
When and why to use AsRef<T> instead of &T
Viewed 0 times
whyanduseasrefwheninstead
Problem
AsRef documentation writesUsed to do a cheap reference-to-reference conversion.
I understand
reference-to-reference part what does it mean by cheap? I hope it has nothing to do with complexity theoretic (big Oh,. etc) "cheapness".Example:
struct User {
email: String,
age: u8,
}
impl AsRef for User {
fn as_ref(&self) -> &User {
&self
}
}
fn main() {
let user = User { email: String::from("myemail@example.com"), age: 25 };
let user_ref = &user;
//...
}
What is the reason to implement
AsRef for User if I can take a reference by simply &user?What is the rule for implementing
AsRef?PS: I couldn't find anything answering these questions in other forums and docs.
Solution
As you've noted,
Let's say we're starting to write an app and we only have
Let's say some time passes and we write a bunch of functions and our app gets really popular and we decide we need to allow users to become moderators and moderators could have different moderation privileges. We could model that like this:
Now we could have just added the
It would be really nice if we could just pass
Now we can pass a
The reason why
Which basically just says if we have some
impl AsRef for User seems a little pointless since you can just do &user. You could do impl AsRef for User or impl AsRef for User as alternatives to &user.email and &user.age but those examples are probably misuses of the trait. What does it mean to be able to convert a User to an &String? Is the &String their email, their first name, their last name, their password? It doesn't make a lot of sense, and falls apart the moment a User has more than one String field.Let's say we're starting to write an app and we only have
Users with emails and ages. We'd model that in Rust like this:struct User {
email: String,
age: u8,
}
Let's say some time passes and we write a bunch of functions and our app gets really popular and we decide we need to allow users to become moderators and moderators could have different moderation privileges. We could model that like this:
struct User {
email: String,
age: u8,
}
enum Privilege {
// imagine different moderator privileges here
}
struct Moderator {
user: User,
privileges: Vec,
}
Now we could have just added the
privileges vector directly into the User struct but since less than 1% of Users will be Moderators it seems like a waste of memory to add a vector to every single User. The addition of the Moderator type causes us to write slightly awkward code though because all of our functions still take Users so we have to pass &moderator.user to them:#[derive(Default)]
struct User {
email: String,
age: u8,
}
enum Privilege {
// imagine different moderator privileges here
}
#[derive(Default)]
struct Moderator {
user: User,
privileges: Vec,
}
fn takes_user(user: &User) {}
fn main() {
let user = User::default();
let moderator = Moderator::default();
takes_user(&user);
takes_user(&moderator.user); // awkward
}
It would be really nice if we could just pass
&moderator to any function expecting an &User because moderators are really just users with just a few added privileges. With AsRef we can! Here's how we'd implement that:#[derive(Default)]
struct User {
email: String,
age: u8,
}
// obviously
impl AsRef for User {
fn as_ref(&self) -> &User {
self
}
}
enum Privilege {
// imagine different moderator privileges here
}
#[derive(Default)]
struct Moderator {
user: User,
privileges: Vec,
}
// since moderators are just regular users
impl AsRef for Moderator {
fn as_ref(&self) -> &User {
&self.user
}
}
fn takes_user>(user: U) {}
fn main() {
let user = User::default();
let moderator = Moderator::default();
takes_user(&user);
takes_user(&moderator); // yay
}
Now we can pass a
&Moderator to any function expecting a &User and it only required a small code refactor. Also, this pattern now scales to arbitrarily many user types, we can add Admins and PowerUsers and SubscribedUsers and as long as we implement AsRef for them they will work with all of our functions.The reason why
&Moderator to &User works out of the box without us having to write an explicit impl AsRef for &Moderator is because of this generic blanket implementation in the standard library:impl AsRef for &T
where
T: AsRef,
{
fn as_ref(&self) -> &U {
>::as_ref(*self)
}
}
Which basically just says if we have some
impl AsRef for T we also automatically get impl AsRef for &T for all T for free.Context
Stack Overflow Q#66026309, score: 141
Revisions (0)
No revisions yet.