基础 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: ?Sized
的trait 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 进行类型转换。
From
和 Into
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(); // 这里需要显式地类型标注
}
TryFrom
和 TryInto
类似于 From
和 Into
,TryFrom
和 TryInto
是 类型转换的通用 trait。不同于 From
/Into
的是,TryFrom
和 TryInto
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
进行转换,如果转换成功那么返回,转换后的值,如果转换失败返回错误信息。
ToString
和 FromStr
要把任何类型转换成 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);
}
ASRef
和 AsMut
ASRef
和 AsMut
用来进行轻便的、引用到引用的转换,具体来说,AsRef将一个不可变的值的引用转换为另一个不可变的引用,而AsMut对可变的引用做同样的转换。
AsRef和AsMut的定义中泛型参数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)引用。