Как вы заметили, impl AsRef<User> for User
кажется немного бессмысленным, поскольку вы можете просто сделать &user
. Вы можете использовать impl AsRef<String> for User
или impl AsRef<u8> for User
в качестве альтернативы &user.email
и &user.age
, но эти примеры, вероятно, являются неправильным использованием черты. Что означает значит возможность преобразования User
в &String
? Является ли &String
их электронной почтой, их именем, их фамилией, их паролем? Это не имеет большого смысла и разваливается в тот момент, когда User
имеет более одного поля String
.
Допустим, мы начинаем писать приложение, и у нас есть только User
с электронной почтой и возрастом. Мы бы смоделировали это в Rust следующим образом:
struct User {
email: String,
age: u8,
}
Допустим, проходит какое-то время, и мы пишем кучу функций, и наше приложение становится очень популярным, и мы решаем, что нам нужно разрешить пользователям становиться модераторами, а модераторы могут иметь разные привилегии модерации. Мы могли бы смоделировать это следующим образом:
struct User {
email: String,
age: u8,
}
enum Privilege {
// imagine different moderator privileges here
}
struct Moderator {
user: User,
privileges: Vec<Privilege>,
}
Теперь мы могли просто добавить вектор privileges
непосредственно в структуру User
, но поскольку менее 1% User
будут Moderator
, добавление вектора к каждому отдельному User
кажется пустой тратой памяти. Добавление типа Moderator
заставляет нас писать немного неуклюжий код, потому что все наши функции по-прежнему принимают User
s, поэтому мы должны передать им &moderator.user
:
#[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
}
Было бы очень хорошо, если бы мы могли просто передать &moderator
любой функции, ожидающей &User
, потому что модераторы на самом деле просто пользователи с несколькими дополнительными привилегиями. С AsRef
мы можем! Вот как мы это реализуем:
#[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
}
Теперь мы можем передать &Moderator
любой функции, ожидающей &User
, и для этого потребовался лишь небольшой рефакторинг кода. Кроме того, этот шаблон теперь масштабируется для любого количества типов пользователей, мы можем добавить Admin
s, PowerUser
s и SubscribedUser
s, и пока мы реализуем для них AsRef<User>
, они будут работать со всеми нашими функциями.
Причина, по которой &Moderator
в &User
работает из коробки без необходимости писать явный impl AsRef<User> for &Moderator
, заключается в общая реализация в стандартной библиотеке:
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)
}
}
Что в основном просто говорит, что если у нас есть impl AsRef<U> for T
, мы также автоматически получаем impl AsRef<U> for &T
для всех T
бесплатно.
person
pretzelhammer
schedule
03.02.2021