FromInto

让我们回到字符串之旅开始的地方:

let ticket = Ticket::new(
    "A title".into(), 
    "A description".into(), 
    "To-Do".into()
);

我们现在已经知道得够多,可以开始拆解这里的 .into() 到底在做什么了。

问题 (The problem)

这是 new 方法的签名:

impl Ticket {
    pub fn new(
        title: String, 
        description: String, 
        status: String
    ) -> Self {
        // [...]
    }
}

我们也已经看到字符串字面量(如 "A title")的类型是 &str
这里有类型不匹配:期望的是 String,但我们手上的是 &str。 这次没有什么神奇的强制转换 (coercion) 来救场;我们需要执行一次转换 (perform a conversion)

FromInto

Rust 标准库在 std::convert 模块中定义了两个用于不会失败的转换 (infallible conversion) 的特质:FromInto

pub trait From<T>: Sized {
    fn from(value: T) -> Self;
}

pub trait Into<T>: Sized {
    fn into(self) -> T;
}

这些特质定义展示了几个我们之前没见过的概念:父特质 (supertrait)隐式特质约束 (implicit trait bound)。 我们先把它们拆开来看。

父特质 / 子特质 (Supertrait / Subtrait)

From: Sized 这个语法意味着 FromSized子特质 (subtrait):任何实现了 From 的类型也必须实现 Sized。 反过来你也可以说:SizedFrom父特质 (supertrait)

隐式特质约束 (Implicit trait bounds)

每当你拥有泛型类型参数时,编译器会隐式假设它是 Sized 的。

例如:

pub struct Foo<T> {
    inner: T,
}

实际上等价于:

pub struct Foo<T: Sized> 
{
    inner: T,
}

对于 From<T>,这条特质定义等价于:

pub trait From<T: Sized>: Sized {
    fn from(value: T) -> Self;
}

换句话说,T 以及 实现 From<T> 的类型都必须是 Sized 的, 即使前一条约束是隐式的。

否定特质约束 (Negative trait bounds)

你可以通过否定特质约束 (negative trait bound) 来取消隐式的 Sized 约束:

pub struct Foo<T: ?Sized> {
    //            ^^^^^^^
    //            这是一个否定特质约束
    inner: T,
}

这个语法读作"T 可能是 Sized,也可能不是",并且允许你把 T 绑到 DST 上(例如 Foo<str>)。这是一个特殊情况:否定特质约束是 Sized 专属的,不能用在其他特质上。

&strString (&str to String)

std 文档中你可以看到哪些 std 类型实现了 From 特质。
你会发现 String 实现了 From<&str> for String。因此我们可以写:

let title = String::from("A title");

不过我们一直主要在用 .into()
如果你查看 Into 的实现者列表, 你不会找到 Into<String> for &str。这是怎么回事?

FromInto对偶的特质 (dual traits)
具体来说,Into 通过一条全覆盖实现 (blanket implementation) 自动地为任何实现了 From 的类型实现:

impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}

如果类型 U 实现了 From<T>,那么 Into<U> for T 会自动被实现。这就是为什么我们可以写 let title = "A title".into();

.into()

每当你看到 .into() 时,你正在见证一次类型之间的转换。
但目标类型是什么呢?

大多数情况下,目标类型要么是:

  • 由函数/方法的签名指定(例如上面例子里的 Ticket::new
  • 或在变量声明中通过类型注解指定(例如 let title: String = "A title".into();

只要编译器能从上下文中无歧义地推断出目标类型,.into() 就会开箱即用。

原文链接:英文原文