Appearance
26.1 类型转换
Rust 给我们提供了一个关键字as
用于基本类型的转换。但是除了基本类型之外,还有更多的自定义类型,它们之间也经常需要做类型转换。为此,Rust 标准库给我们提供了一系列的 trait 来辅助抽象。
26.1.1 AsRef/AsMut
AsRef 这个 trait 代表的意思是,这个类型可以通过调用as_ref
方法,得到另外一个类型的共享引用。它的定义如下:
rust
pub trait AsRef<T: ?Sized> {
fn as_ref(&self) -> &T;
}
同理,AsMut 有一个 as_mut 方法,可以得到另外一个类型的可读写引用:
rust
pub trait AsMut<T: ?Sized> {
fn as_mut(&mut self) -> &mut T;
}
比如说,标准库里面的 String 类型,就针对好几个类型参数实现了 AsRef trait:
rust
impl AsRef<str> for String
impl AsRef<[u8]> for String
impl AsRef<OsStr> for String
impl AsRef<Path> for String
AsRef 这样的 trait 很适合用在泛型代码中,为一系列类型做统一抽象。比如,我们可以写一个泛型函数,它接受各种类型,只要可以被转换为&[u8]
即可:
rust
fn iter_bytes<T: AsRef<[u8]>>(arg: T) {
for i in arg.as_ref() {
println!("{}", i);
}
}
fn main() {
let s: String = String::from("this is a string");
let v: Vec<u8> = vec![1,2,3];
let c: &str = "hello";
// 相当于函数重载。只不过基于泛型实现的重载,一定需要重载的参数类型满足某种共同的约束
iter_bytes(s);
iter_bytes(v);
iter_bytes(c);
}
26.1.2 Borrow/BorrowMut
Borrow 这个 trait 设计得与 AsRef 非常像。它是这样定义的:
rust
pub trait Borrow<Borrowed: ?Sized> {
fn borrow(&self) -> &Borrowed;
}
可以说,除了名字之外,它和 AsRef 长得一模一样。但它们的设计意图不同。比如,针对 String 类型,它只实现了一个Borrow<str>
:
rust
impl Borrow<str> for String
这是因为这个 trait 一般被用于实现某些重要的数据结构,比如 HashMap:
rust
impl HashMap {
pub fn get<Q: ?Sized>(&self, k: &Q) -> Option<&V>
where K: Borrow<Q>,
Q: Hash + Eq
{}
}
和 BTreeMap:
rust
impl BTreeMap {
pub fn get<Q: ?Sized>(&self, key: &Q) -> Option<&V>
where K: Borrow<Q>,
Q: Ord
{}
}
所以,它要求borrow()
方法返回的类型,必须和原来的类型具备同样的 hash 值,以及排序。这是一个约定,如果实现 Borrow trait 的时候违反了这个约定,那么把这个类型放到 HashMap 或者 BTreeMap 里面的时候就可能出现问题。
26.1.3 From/Into
AsRef/Borrow 做的类型转换都是从一种引用&T
到另一种引用&U
的转换。而 From/Into 做的则是从任意类型 T 到 U 的类型转换:
rust
pub trait From<T> {
fn from(T) -> Self;
}
pub trait Into<T> {
fn into(self) -> T;
}
显然,From 和 Into 是互逆的一组转换。如果 T 实现了From<U>
,那么 U 理应实现Into<T>
。因此,标准库里面提供了这样一个实现:
rust
impl<T, U> Into<U> for T where U: From<T>
{
fn into(self) -> U {
U::from(self)
}
}
用自然语言描述,意思就是:如果存在U:From<T>
,则实现T:Into<U>
。
正是因为标准库中已经有了这样一个默认实现,我们在需要给两个类型实现类型转换的 trait 的时候,写一个 From 就够了,Into 不需要自己手写。
比如,标准库里面已经给我们提供了这样的转换:
rust
impl<'a> From<&'a str> for String
这意味着&str 类型可以转换为 String 类型。我们有两种调用方式:一种是通过String::from(&str)
来使用,一种是通过&str::into()
来使用。它们的意思一样:
rust
fn main() {
let s: &'static str = "hello";
let str1: String = s.into();
let str2: String = String::from(s);
}
另外,由于这几个 trait 很常用,因此 Rust 已经将它们加入到 prelude 中。在使用的时候我们不需要写use std::convert::From;
这样的语句了,包括 AsRef、AsMut、Into、From、ToOwned 等。具体可以参见libstd/prelude/v1.rs
源代码的内容。
标准库中还有一组对应的 TryFrom/TryInto 两个 trait,它们是为了处理那种类型转换过程中可能发生转换错误的情况。因此,它们的方法的返回类型是 Result 类型。
26.1.4 ToOwned
ToOwned trait 提供的是一种更“泛化”的 Clone 的功能。Clone 一般是从&T
类型变量创造一个新的 T 类型变量,而 ToOwned 一般是从一个&T 类型变量创造一个新的 U 类型变量。
在标准库中,ToOwned 有一个默认实现,即调用clone
方法:
rust
impl<T> ToOwned for T
where T: Clone
{
type Owned = T;
fn to_owned(&self) -> T {
self.clone()
}
fn clone_into(&self, target: &mut T) {
target.clone_from(self);
}
}
但是,它还对一些特殊类型实现了这个 trait。比如:
rust
impl<T: Clone> ToOwned for [T] {
type Owned = Vec<T>;
}
impl ToOwned for str {
type Owned = String;
}
而且,很有用的类型 Cow 也是基于 ToOwned 实现的:
rust
pub enum Cow<'a, B>
where
B: 'a + ToOwned + ?Sized,
{
Borrowed(&'a B),
Owned(<B as ToOwned>::Owned),
}
26.1.5 ToString/FromStr
ToString trait 提供了其他类型转换为 String 类型的能力。
rust
pub trait ToString {
fn to_string(&self) -> String;
}
一般情况下,我们不需要自己为自定义类型实现 ToString trait。因为标准库中已经提供了一个默认实现:
rust
impl<T: fmt::Display + ?Sized> ToString for T {
#[inline]
default fn to_string(&self) -> String {
use core::fmt::Write;
let mut buf = String::new();
buf.write_fmt(format_args!("{}", self))
.expect("a Display implementation return an error unexpectedly");
buf.shrink_to_fit();
buf
}
}
这意味着,任何一个实现了 Display trait 的类型,都自动实现了 ToString trait。而 Display trait 是可以自动 derive 的,我们只需要为类型添加一个 attribute 即可。
FromStr 则提供了从字符串切片&str
向其他类型转换的能力。
rust
pub trait FromStr {
type Err;
fn from_str(s: &str) -> Result<Self, Self::Err>;
}
因为这个转换过程可能出错,所以from_str
方法的返回类型被设计为 Result。
正是因为有了这个 trait,所以 str 类型才有了一个成员方法 parse:
rust
pub fn parse<F: FromStr>(&self) -> Result<F, F::Err> { … }
所以我们可以写下面这样非常清晰直白的代码:
rust
let four = "4".parse::<u32>();
assert_eq!(Ok(4), four);