何时以及为何使用 AsRef<T> 而不是 &T

When and why to use AsRef<T> instead of &T

AsRef 文档写道

Used to do a cheap reference-to-reference conversion.

我明白 reference-to-reference 部分 cheap 是什么意思?我希望它与复杂性理论(大哦等)“廉价”无关。

示例:

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("myemail@example.com"), age: 25 };
  let user_ref = &user;
  //...
}

如果我可以简单地通过&user作为参考,为什么要为User实施AsRefAsRef的执行规则是什么?

PS:我在其他论坛和文档中找不到任何可以回答这些问题的内容。

I hope it has nothing to do with complexity theoretic (big Oh,. etc) "cheapness".

绝对是。 AsRef 旨在不花任何钱。

What is reason to implement AsRef for User if I can take a reference by simply &user?

可能没有。 AsRef 对于通用代码很有用,尤其是(但不限于)人体工程学。

例如,如果 std::fs::rename 拿了一个 &Path 你必须写:

fs::rename(Path::new("a.txt"), Path::new("b.txt"))?;

冗长且烦人。

然而,由于它 确实 采用 AsRef<Path> 而不是它可以使用字符串开箱即用,这意味着您可以调用:

fs::rename("a.txt", "b.txt")?;

非常简单易读。

正如您所指出的,impl AsRef<User> for User 似乎有点毫无意义,因为您可以做到 &user。您可以使用 impl AsRef<String> for Userimpl AsRef<u8> for User 作为 &user.email&user.age 的替代方法,但这些示例可能是对特征的滥用。能够将 User 转换为 &String 是什么意思 &String 是他们的电子邮件、名字、姓氏和密码吗?它没有多大意义,并且在 User 具有多个 String 字段的那一刻就崩溃了。

假设我们开始编写一个应用程序,我们只有 Users 包含电子邮件和年龄。我们会像这样在 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结构中,但由于User的不到1%将是 Moderators 向每个 User 添加向量似乎是在浪费内存。 Moderator 类型的添加导致我们编写的代码有点笨拙,因为我们所有的函数仍然需要 Users,所以我们必须将 &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 的函数,而且只需要进行少量代码重构。此外,这种模式现在可以扩展到任意多的用户类型,我们可以添加 Admins 和 PowerUsers 和 SubscribedUsers,只要我们为他们实现 AsRef<User>,他们就会使用我们的所有功能。

为什么 &Moderator&User 开箱即用而不需要我们写一个明确的 impl AsRef<User> for &Moderator 是因为标准库中的 this generic blanket implementation:

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 我们也会自动免费获得所有 Timpl AsRef<U> for &T