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

Har*_*ork 7 generics polymorphism traits rust parametric-polymorphism

AsRef 文档写入

用于进行廉价的引用到引用转换。

我明白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;
  //...
}
Run Code Online (Sandbox Code Playgroud)

什么是执行的原因AsRefUser,如果我可以随便拿一个参考&user?执行的规则是AsRef什么?

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

Mas*_*inn 19

我希望它与复杂性理论(大哦,等等)“便宜”无关。

确实如此。AsRef基本上不需要任何成本。

如果我可以简单地通过 &user 进行引用,那么为什么要为 User 实现 AsRef 呢?

应该没有吧 AsRef对于通用代码很有用,特别是(但不限于)人体工程学。

例如,如果std::fs::rename采取了&Path你必须写:

fs::rename(Path::new("a.txt"), Path::new("b.txt"))?;
Run Code Online (Sandbox Code Playgroud)

这是冗长且烦人的。

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

fs::rename("a.txt", "b.txt")?;
Run Code Online (Sandbox Code Playgroud)

这是非常简单并且更具可读性的。

  • @user4815162352 不,这有点不寻常,但 `Path::new` 实际上返回一个 `&amp;Path`。它不能这样做,因为“Path”未调整大小,裸露的“Path”本质上与裸露的“str”相同。 (5认同)

kfe*_*v91 15

正如您所指出的,impl AsRef<User> for User似乎有点毫无意义,因为您可以只做&user. 你可以做impl AsRef<String> for Userimpl AsRef<u8> for User作为替代品&user.email&user.age但这些例子可能是对特征的滥用。能够将 a 转换为 an是什么意思?是他们的电子邮件、他们的名字、他们的姓氏、他们的密码吗?它没有多大意义,并且在 a拥有多个领域时分崩离析。User&String&StringUserString

假设我们开始编写一个应用程序,我们只有User电子邮件和年龄。我们会像这样在 Rust 中建模:

struct User {
    email: String,
    age: u8,
}
Run Code Online (Sandbox Code Playgroud)

假设一段时间过去了,我们编写了一堆函数,我们的应用程序变得非常流行,我们决定允许用户成为版主,版主可以拥有不同的版主权限。我们可以这样建模:

struct User {
    email: String,
    age: u8,
}

enum Privilege {
    // imagine different moderator privileges here
}

struct Moderator {
    user: User,
    privileges: Vec<Privilege>,
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以privileges直接将向量添加到User结构体中,但由于少于 1% 的Users 将是Moderators,因此向每个User. 添加的Moderator类型会导致我们写略显尴尬的代码,但因为我们所有的功能仍然采取User如此的,我们必须通过&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
}
Run Code Online (Sandbox Code Playgroud)

如果我们可以传递&moderator给任何需要 an 的函数,那就太好了,&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
}
Run Code Online (Sandbox Code Playgroud)

现在我们可以将 a 传递&Moderator给任何&User需要 a 的函数,它只需要一个小的代码重构。此外,这种模式现在可以扩展到任意多种用户类型,我们可以添加Admins 和PowerUsers 和SubscribedUsers,只要我们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)
    }
}
Run Code Online (Sandbox Code Playgroud)

这基本上是说,如果我们有一些impl AsRef<U> for T我们还可以自动获得impl AsRef<U> for &T所有T免费的。

  • @HardFork是的,这是该特征的主要用例,用于编写通用函数。如果您编写任何与文件系统一起使用的 Rust 程序,您会注意到许多标准库函数和方法都是通用的,并采用“&lt;P: AsRef&lt;Path&gt;&gt;”作为参数。 (3认同)
  • @HardFork您更改了函数签名,因此它不再适用于普通用户,现在传递“&amp;User”是一个编译时错误。 (2认同)