Skip to content

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);

Released under the MIT License