Reputation: 1083
AsRef
documentation writes
Used 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<User> for User {
fn as_ref(&self) -> &User {
&self
}
}
fn main() {
let user = User { email: String::from("[email protected]"), 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.
Upvotes: 69
Views: 26584
Reputation: 42472
I hope it has nothing to do with complexity theoretic (big Oh,. etc) "cheapness".
It absolutely does. AsRef
is intended to cost essentially nothing.
What is reason to implement AsRef for User if I can take a reference by simply &user?
There probably isn't one. AsRef
is useful for generic code, especially (though not exclusively) for ergonomics.
For instance if std::fs::rename
took an &Path
you'd have to write:
fs::rename(Path::new("a.txt"), Path::new("b.txt"))?;
which is verbose and annoying.
However since it does take AsRef<Path>
instead it works out of the box with strings, meaning you can just call:
fs::rename("a.txt", "b.txt")?;
which is perfectly straightforward and much more readable.
Upvotes: 25
Reputation: 15135
As you've noted, impl AsRef<User> for User
seems a little pointless since you can just do &user
. You could do impl AsRef<String> for User
or impl AsRef<u8> 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 User
s 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<Privilege>,
}
Now we could have just added the privileges
vector directly into the User
struct but since less than 1% of User
s will be Moderator
s 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 User
s 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<Privilege>,
}
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<User> for User {
fn as_ref(&self) -> &User {
self
}
}
enum Privilege {
// imagine different moderator privileges here
}
#[derive(Default)]
struct Moderator {
user: User,
privileges: Vec<Privilege>,
}
// since moderators are just regular users
impl AsRef<User> for Moderator {
fn as_ref(&self) -> &User {
&self.user
}
}
fn takes_user<U: AsRef<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 Admin
s and PowerUser
s and SubscribedUser
s and as long as we implement AsRef<User>
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<User> for &Moderator
is because of this generic blanket implementation in the standard library:
impl<T: ?Sized, U: ?Sized> AsRef<U> for &T
where
T: AsRef<U>,
{
fn as_ref(&self) -> &U {
<T as AsRef<U>>::as_ref(*self)
}
}
Which basically just says if we have some impl AsRef<U> for T
we also automatically get impl AsRef<U> for &T
for all T
for free.
Upvotes: 127