FromInto

让我们回到旅程的起点:

#![allow(unused)]
fn main() {
let ticket = Ticket::new("A title".into(), "A description".into(), "To-Do".into());
}

我们现在足以解开.into()在这里的作用了。

问题所在

这是new方法的签名:

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

我们也了解到字符串字面量,如"一个标题"是类型&str。这里类型不匹配:期望一个String,但我们有一个&str。这次没有魔法会拯救我们,我们需要进行转换

FromInto

Rust标准库为**可转换**定义了两个特性:FromInto,位于std::convert`模块中。

#![allow(unused)]
fn main() {
pub trait From<T> {
    fn from(value: T) -> Self;}

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

这些定义展示了我们之前未见过的几个概念:Supertrait泛型隐式约束。我们先来拆解这些。

Supertrait / Subtrait

From: Sized语法意味着From是一个SubtraitSized:任何实现了From的类型也必须实现Sized。或者可以说SizedFromSupertrait

泛型

FromInto都是泛型特性**。它们带有一个参数T,代表转换的类型。T是一个占位符,实际类型,将在实现或使用特性时指定。

隐式约束

每次有泛型参数时,编译器都默认它实现了Sized

例如:

#![allow(unused)]
fn main() {
pub struct Foo<T> {
    inner: T,
}````

实际上等同于:

```rust
pub struct Foo<T> 
where
    T: Sized,
    // ^^^^^^ 这被称为**约束**
    // 它指定此实现仅适用于 // 类型T`实现`Sized`
    // 你可以要求多个特性被实现 // 使用+`符号 // 如`Sized + PartialEq<T>`
{
    inner: T,
}
}

你可以通过负约束(negative trait bound)来选择退出这个行为:

#![allow(unused)]
fn main() {
// 你也可以内联行约束,
// 而不是使用`where`子句

pub struct Foo<Sized> {
    // ^^^^^^ // 这是一个负约束
    // 它读作“T`可能不是”Sized”, // 并允许你绑定T到 DST(如`Foo<str>)
    inner: T,
}
}

From<T>情况下,我们希望T和实现From<T>类型都Sized,尽管后者是隐式的。

&strString

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

#![allow(unused)]
fn main() {
let title = String::from("A title");
}

我们主要使用了.into()。如果你查看[Into的实现](https://doc.rust-lang.org/std/convert/trait.Into.html),找不到Into<&str> for String`。怎么回事?

FromInto对称特性。特别是,任何实现了From的类型都会自动实现一个**空白实现Into

#![allow(unused)]
fn main() {
impl<T, U> Into<U> for T
where
    U: From<T>,
{
    fn into(self) -> U {
        U::from(self)
    }
}

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

.into()

每次看到.into(),你都在见证了一次类型间的转换。目标类型是什么?

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

  • 函数/方法签名指定(如上例Ticket::new)- 变量声明时类型注解(例let title: String = "A title".into())。

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

参考资料

  • 本节的练习位于 exercises/04_traits/08_from