Skip to content

基础 trait 介绍

Sized trait

在 Rust 里,如果一个类型的字节大小在编译期可以确定,那么这个类型就是确定大小(sized)的。确定类型的大小(size)对于能够在栈(stack)上为实例分配足够的空间是十分重要的。确定大小类型可以通过传值(by value)或者传引用(by reference)的方式来传递。

如果一个类型的大小不能在编译期确定,那么它就被称为不确定大小类型(unsized type)或者DST,即动态大小类型(Dynamically-Sized Type)。因为不确定大小类型(unsized type)不能存放在栈上,所以它们只能通过传引用(by reference)的方式来传递。

Sized trait 用于标记在编译时就能确定占用内存大小的类型。当使用泛型参数时,Rust编译器会自动为泛型参数加上 T: Sized trait bound。例如struct Foo<T>等价于struct Foo<T: Sized>。 如果在某些情况下,无法在编译时确定泛型参数T类型的大小,就需要使用?Sized标记,即如果显式加了T: ?Sizedtrait bound,那么T就可以是任意大小的。

Sized trait 通常被省略,但当显式添加时,也就是说该类型的大小在编译时已知。如果一个类型的所有成员都是Sized,那么这个类型就会自动实现Sized成员(member)的含义取决于具体的类型,例如:一个结构体的字段,枚举的变体(variants),数组的元素,元组(tuple)的项(item)等等。一旦一个类型被一个Sized实现“标记(marked)”则意味着在编译期可以确定它的字节数大小。

也就是说,除了确定大小的类型(sized type)还有不确定大小的类型(unsized type)、动态大小的类型(DST)、可能确定也可能不确定大小的类型(?sized type)、ZST(零大小类型)等。

更多知识请参考学习标准库链接:https://doc.rust-lang.org/std/marker/trait.Sized.html。

类型转换trait

在前面我们讲了基本类型的一些类型转换的方式,除了使用 as 进行类型转换外,还有一些利用 trait 进行类型转换。

FromInto

From trait 允许一种类型定义 “怎么根据另一种类型生成自己”,因此它提供了一种类型转换的简单机制。在标准库中有无数 From 的实现,规定原生类型及其他常见类型的转换功能。

Into trait 就是把 From trait 倒过来而已。也就是说,如果你为你的类型实现了 From,那么同时你也就获得了 Into。使用 Into trait 通常要求指明要转换到的类型,因为编译器大多数时候不能推断它。

比如,可以很容易地把 &str 转换成 String

rust
#![allow(unused)]
fn main() {
    let my_str = "hello";

    // 以下三个转换都依赖于一个事实:String 实现了 From<&str> 特征
    let string1 = String::from(my_str);
    let string2 = my_str.to_string();
    let string3: String = my_str.into();  // 这里需要显式地类型标注
}

TryFromTryInto

类似于 FromIntoTryFromTryInto 是 类型转换的通用 trait。不同于 From/Into 的是,TryFromTryInto trait 用于易出错的转换,也正因如此,其返回值是 Result 型。try_into的使用示例:

rust
fn main() {
    let b: i16 = 1500;

    let b_: u8 = match b.try_into() {
        Ok(b1) => b1,
        Err(e) => {
            println!("{:?}", e.to_string());
            0
        }
    };
}

在上面的示例中,将变量b进行转换,如果转换成功那么返回,转换后的值,如果转换失败返回错误信息。

ToStringFromStr

要把任何类型转换成 String,只需要实现那个类型的 ToString trait。然而不要直接这么做,最好应该实现fmt::Display trait,它会自动提供 ToString,并且还可以用来打印输出。

rust
use std::fmt;

struct Circle {
    radius: i32
}

// 实现 fmt::Display trait
impl fmt::Display for Circle {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "Circle of radius {}", self.radius)
    }
}

fn main() {
    let circle = Circle { radius: 6 };
    println!("{}", circle.to_string());
}

FromStr是 Rust 标准库中定义的 trait,当一个类型实现FromStr trait后,调用字符串的泛型函数str.parse()就可以很方便的实现字符串到某个具体类型的转换。

比如说,我们经常需要把字符串转成数字。完成这项工作的标准手段是用 parse 函数。我们得提供要转换到的类型,这可以通过不使用类型推断,或者用 “涡轮鱼” 语法(turbo fish,<>)实现。

只要对目标类型实现了 FromStr trait,就可以用 parse 把字符串转换成目标类型。 标准库中已经给无数种类型实现了 FromStr。如果要转换到用户定义类型,只要手动实现 FromStr 就行。例如下面的少诶复杂的例子:

rust
#![allow(unused)]
fn main() {
use std::str::FromStr;
use std::num::ParseIntError;

#[derive(Debug, PartialEq)]
struct Point {
    x: i32,
    y: i32
}

// 手动为Point结构体实现 FromStr
impl FromStr for Point {
    type Err = ParseIntError;
    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let (x, y) = s  // 这里对字符串 s,进行前后切割得到元组(x, y)
            .strip_prefix('(')   // 采用标准库函数strip_prefix、strip_suffix、split_once等对字符串先后进行处理
            .and_then(|s| s.strip_suffix(')'))
            .and_then(|s| s.split_once(','))
            .unwrap();
		// 在RU盾他标准库中,为整型已经实现了 fromstr trait,可以直接使用parse方法
        let x_fromstr = x.parse::<i32>()?;
        let y_fromstr = y.parse::<i32>()?;
		// 返回最后结果
        Ok(Point { x: x_fromstr, y: y_fromstr })
    }
}

let expected = Ok(Point { x: 1, y: 2 });
// Explicit call
assert_eq!(Point::from_str("(1,2)"), expected);
// Implicit calls, through parse
assert_eq!("(1,2)".parse(), expected);
assert_eq!("(1,2)".parse::<Point>(), expected);
}

ASRefAsMut

ASRefAsMut用来进行轻便的、引用到引用的转换,具体来说,AsRef将一个不可变的值的引用转换为另一个不可变的引用,而AsMut对可变的引用做同样的转换。

AsRefAsMut的定义中泛型参数T都加了T: ?Sized,所以它们都允许T使用大小可变的类型。

AsRef 提供了一个方法 .as_ref()。在这个方法中,对于一个类型为 T 的对象 foo,如果类型 T 实现了 AsRef<U>,那么,foo 可调用方法as_ref() ,即 foo.as_ref()。操作的结果,我们得到了一个类型为 &U 的新引用,也就是完成了类型的转换。例如:

rust
#![allow(unused)]
fn main() {
    // 约束类型 T 实现了trait AsRef,如果没有实现的话不能进行方法调用
    // 最后会转换为类型U,也就是 &str
    fn is_hello<T: AsRef<str>>(s: T) {
        assert_eq!("hello", s.as_ref());
    }

    let s: &str = "hello";
    is_hello(s);

    // &str和String都实现了 AsRef<str>,所以都可以进行方法调用
    let s: String = "hello".to_string();
    is_hello(s);
}

AsMut<T> 提供了一个方法 .as_mut()。它是 AsRef<T> 的可变(mutable)引用版本。

对于一个类型为 T 的对象 foo,如果 T 实现了 AsMut<U>,那么,foo 可调用方法as_mut() 操作,即 foo.as_mut()。操作的结果,我们得到了一个类型为 &mut U 的可变(mutable)引用。