From
和 Into
让我们回到旅程的起点:
#![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
。这次没有魔法会拯救我们,我们需要进行转换。
From
和 Into
Rust标准库为**可转换**定义了两个特性:
From和
Into,位于
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
。或者可以说Sized
是From
的Supertrait。
泛型
From
和Into
都是泛型特性**。它们带有一个参数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
,尽管后者是隐式的。
&str
到 String
在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`。怎么回事?
From
和Into
是对称特性。特别是,任何实现了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