rust trait
1 定义
- trait在其他语言里,类似接口interface
- trait里定义若干行为或者说功能(在代码中体现为方法)
- 是一种约束
- 类型是对变量申请的内存空间的一种约束
- 泛型可以说就是因为类型约束的过于严格而产生的
- 然后泛型又过于宽泛了,啥都行,我就想要符合某种特征(具有某些行为)的类型,这样就产生了trait用来约束泛型
- 只要实现某个trait
- 就具备了某种能力或功能
- 共享了一些能力: trait 定义里有默认实现的方法,所有实现该trait的类型都可以直接用
- 是一种约束
2 入门例子
- 我们在结构体章节中已经提到, self 相当于 self: Self, Self是一种类型,表示结构体本身
- trait中定义的方法里也是一样的省略写法
- Self 表示实现了该trait的具体类型
- self =
self: Self - &self =
self: &Self - &mut self =
self: &mut Self - 如果trait中定义的方法的第一个参数是以上三种(还有类似这种参数设置 self: Box<Self>), 那么表示这是一个实例方法, 其他情况则称为关联方法(或者称:静态方法)
trait ExampleTrait {
// 没有方法体的方法, 实现该trait的类型 必须实现该方法
// 使用 &self 参数的方法,不修改数据
fn inspect(&self) -> &str;
// 使用 &mut self 参数的方法,可以修改数据
fn modify(&mut self, tmp: &str);
// 使用 self 参数的方法,通常用于消费 trait 对象本身
fn consume(self);
// 有默认实现的方法, 实现该trait的类型可以不用写
// 当然也可以重新写来覆盖这个方法的具体实现
// 可以说是一种代码共享了, 因为可以直接用了
fn say(&self) {
println!("hello rust");
}
// 无接收者的静态方法
fn hello();
// 不是self:&Self, 用其他的名字看看
// 有默认实现
fn world(this: &Self) {
println!("use this");
}
}
struct ExampleStruct {
value: String,
}
// 为ExampleStruct实现 ExampleTrait
impl ExampleTrait for ExampleStruct {
fn inspect(&self) -> &str {
&self.value
}
fn modify(&mut self, tmp: &str) {
self.value.push_str(tmp);
}
fn consume(self) {
println!("Consumed value: {}", self.value);
// `self` 被消费,之后无法使用
}
// 必须实现这个
fn hello(){
println!("hello example");
}
}
fn main() {
let mut example = ExampleStruct {
value: "Hello".to_string(),
};
// 直接调用trait里的已经实现了的方法
example.say();
ExampleStruct::hello(); // 调用静态函数
// 第一个参数名不是 self, 用其他, 只能用这种方式, 不能用 实例.方法()
ExampleTrait::world(&example); // 调用trait 有默认实现的静态方法
// 调用 inspect 方法, 相当于 ExampleTrait::inspect(&example)
println!("Before modification: {}", example.inspect());
println!("Before modification: {}", ExampleTrait::inspect(&example));
// 调用 modify 方法
example.modify(" world");
// 调用 inspect 方法,查看修改结果
println!("After modification: {}", example.inspect());
// 相当于
ExampleTrait::modify(&mut example, " rust");
println!("After modification: {}", example.inspect());
// 调用 consume 方法,这会消费 example,之后不能再使用 example
example.consume();
// 下面这行代码如果取消注释会导致编译错误,因为 example 已经被消费了
// println!("Cannot use example after consume: {}", example.inspect());
}trait方法第一个参数的变量名是self, 但是类型是这种Box<Self> 的情况
trait ExampleTrait {
fn consume(self: Box<Self>);
}
struct ExampleStruct {
value: String,
}
impl ExampleTrait for ExampleStruct {
fn consume(self: Box<Self>) {
println!("Consumed value: {}", (*self).value);
println!("Consumed value: {}", self.value);
}
}
fn main() {
let example = Box::new(ExampleStruct {
value: "Hello".to_string(),
});
example.consume();
}3 给内置或外部类型实现trait
3.1 为i32 实现一个我们定义的trait
3.2 孤儿规则 (orphan rule)
- 如果你想要为类型A实现trait B,那么要么类型A是在当前的crate中定义的,要么trait B是在当前的crate中定义的
- 如果要实现外部定义的trait需要先将其导入作用域
- 再换句话说就是
- 可以对外部类型实现自定义的trait
- 可以对自定义类型上实现外部trait
- 外部是指不是由当前crate,而是由外部定义的,比如标准库
3.3 newtype”绕过”孤儿规则
use std::fmt;
// only traits defined in the current crate can be implemented for types defined outside of the crate
// define and implement a trait or new type instead
// impl fmt::Display for Vec<String> {
// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
// write!(f, "[{}]", self.0.join(", "))
// }
// }
// 使用元祖结构体 变成一个新的类型,是本地的
// Vec 是标准库里的,属于外部类型
struct Wrapper(Vec<String>);
// fmt::Display 这个是外部trait
impl fmt::Display for Wrapper {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "[{}]", self.0.join(", "))
}
}
fn main() {
let w = Wrapper(vec![String::from("hello"), String::from("world")]);
println!("w = {}", w);
}4 完全限定语法
- 当一个结构体实现了多个trait,而这些trait里有相同的方法,无法直接用实例.方法()来调用,那么如何调用这些方法呢?
// 定义第一个trait,它有一个名为`announce`的方法
trait TraitOne {
fn announce(&self);
}
// 定义第二个trait,它也有一个名为`announce`的方法
trait TraitTwo {
fn announce(&self);
}
// 定义一个结构体`Speaker`
struct Speaker {
message: String,
}
// 为`Speaker`实现`TraitOne`
impl TraitOne for Speaker {
fn announce(&self) {
println!("traitOne: {}", &self.message)
}
}
// 为`Speaker`实现`TraitTwo`
impl TraitTwo for Speaker {
fn announce(&self) {
println!("traitTwo: {}", &self.message)
}
}
fn main() {
// 创建一个`Speaker`实例
let speaker = Speaker {
message: "Hello".to_string(),
};
// speaker.announce(); 报错了 提示用多个announce
// 使用完全限定语法调用`TraitOne`的`announce`方法
<Speaker as TraitOne>::announce(&speaker);
TraitOne::announce(&speaker);
// 使用完全限定语法调用`TraitTwo`的`announce`方法
<Speaker as TraitTwo>::announce(&speaker);
TraitTwo::announce(&speaker);
}5 trait 作为约束
5.1 impl 写法
参数item, 必须是实现了Summary这个trait的类型,这里是该类型的可变借用
5.2 泛型 写法
impl Summary 相比较这个写法, 我们一般用下面这样的写法
5.3 where 写法
当我们在指定多个参数 然后每个参数要实现多个trait时, 写起来可能不是那么清晰, 这时我们可以使用where来简化
6 trait的不同形式
6.1 简单 trait
入门例子中举的例子就属于非常简单的类型
6.2 泛型 trait
6.2.1 产生原因和使用方式
不使用泛型的情况
#[derive(Debug)]
struct Number {
value: i32,
}
// 定义一个trait
trait MyFrom {
// 这个"行为"的想法是 想从类型i32,生成一个类型 Self (即: 实现该trait的主体)
fn my_from(item: i32) -> Self;
}
impl MyFrom for Number {
fn my_from(item: i32) -> Self {
Self { value: item }
}
}
#[derive(Debug)]
struct MyString {
value: String,
}
trait MyFrom2 {
// 由于类型不同, 我们不得不 重新定义个 trait
// 里面的"行为" 实际它的逻辑一样, 都是从一个类型生成 Self 类型
// 但是我们不得不重新写一遍
fn my_from(item: String) -> Self;
}
impl MyFrom2 for MyString {
fn my_from(item: String) -> Self {
Self { value: item }
}
}
fn main() {
let num = Number::my_from(30);
let str2 = MyString::my_from("hello".to_string());
println!("My number is {:?}", num);
println!("My string is {:?}", str2);
}- 可以从一种类型生成另外一种类型, 这个行为应该是非常抽象的, 是说几乎每个类型都可以有这样一个行为
使用泛型参数的trait
#[derive(Debug)]
struct Number {
value: i32,
}
// trait 定义的行为 依赖于多个不同类型, 我们就使用泛型
trait MyFrom<T> {
fn my_from(item: T) -> Self;
}
impl MyFrom<i32> for Number {
fn my_from(item: i32) -> Self {
Number { value: item }
}
}
#[derive(Debug)]
struct MyString {
value: String,
}
impl MyFrom<String> for MyString {
fn my_from(item: String) -> Self {
Self { value: item }
}
}
fn main() {
let num = Number::my_from(30);
let str2 = MyString::my_from("hello".to_string());
println!("My number is {:?}", num);
println!("My string is {:?}", str2);
}use std::fmt::Display;
trait Animal<T>
where
T: Display,
{
fn shout(&self, v: T);
}
struct Dog;
// impl<T> 中的<T> 是申明泛型
// Aniaml<T> 中的T 就已经是在使用了,不是申明
impl<T> Animal<T> for Dog
where
T: Display,
{
fn shout(&self, v: T) {
println!("{}", v);
}
}
// 这里会报错, 因为 前面 的 `impl<T> Animal<T> for Dog` 已经包含了这里的情况
// 如果你 上面的不要写, 单独为Dog 实现多个 trait, `impl Animal<String>` ,`impl Animal<i32>`这样具化类型, 是可以的
// impl Animal<String> for Dog {
// fn shout(&self, v: String) {
// println!("{}", v);
// }
// }
fn main() {
let a = Dog;
// Dog 的实例实现了 trait (Animal<T>)的shout 方法
// shout 使用的参数类型, 具化了 T
// 下面2个都ok
a.shout(123);
a.shout("wang wang");
}6.2.2 设置泛型默认值
trait Summable<T = i32> {
fn sum(&self) -> T;
}
// 实现Summable trait,使用默认的泛型类型 i32
impl Summable for Vec<i32> {
fn sum(&self) -> i32 {
self.iter().sum()
}
}
// 实现Summable trait,但为泛型指定一个不同的类型 f64
impl Summable<f64> for Vec<f64> {
fn sum(&self) -> f64 {
self.iter().sum()
}
}
fn main() {
let vec_i32: Vec<i32> = vec![1, 2, 3];
let vec_f64: Vec<f64> = vec![1.0, 2.0, 3.0];
// 使用默认的泛型类型 i32
println!("Sum of vec_i32: {}", vec_i32.sum());
// 使用指定的泛型类型 f64
println!("Sum of vec_f64: {}", vec_f64.sum());
}6.3 关联类型 trait
6.3.1 产生的原因和使用
// 有一个trait, 需要多个泛型参数
trait ExampleTrait<A, B, C> {
fn tmp(&self, x: A, y: B) -> C;
fn get(&self) -> C;
}
struct ExampleStruct {}
// 每次你为某个结构体实现这个trait时, 就需要写 很多个泛型参数
// 很麻烦
impl ExampleTrait<i32, i64, i8> for ExampleStruct {
fn tmp(&self, x: i32, y: i64) -> i8 {
6i8
}
fn get(&self) -> i8 {
self.tmp(1, 2)
}
}
// 当你写一个函数, 参数类型需要 实现ExampleTrait 的时候
// 你需要这样写上一堆 泛型参数, 也很麻烦
fn test<A, B, C, D>(x: &D) -> C
where
D: ExampleTrait<A, B, C>,
{
x.get()
}使用关联类型
trait ExampleTrait {
type A; // 像占位符一样
type B;
type C;
fn tmp(&self, x: Self::A, y: Self::B) -> Self::C;
fn get(&self) -> Self::C;
}
struct ExampleStruct1 {}
impl ExampleTrait for ExampleStruct1 {
// 给具体类型实现 trait的时候
// 我们就写成具体的类型
type A = i32;
type B = i64;
type C = i8;
// 里面可以直接写 具体的类型
fn tmp(&self, x: i32, y: i64) -> i8 {
// ... 这里无需关心具体实现.. 我们就只是说明关联类型的优点
6i8
}
fn get(&self) -> i8 {
self.tmp(1, 2)
}
}
struct ExampleStruct2 {}
impl ExampleTrait for ExampleStruct2 {
type A = i32;
type B = i64;
type C = i8;
// 当然可以写 Self::A 这种 (和前面写具体类型都是可以的)
fn tmp(&self, x: Self::A, y: Self::B) -> Self::C {
1i8
}
fn get(&self) -> Self::C {
self.tmp(1, 2)
}
}
// 有一个函数, 参数类型需要 实现ExampleTrait2
fn test<D>(x: &D) -> D::C
where
D: ExampleTrait,
{
x.get()
}
fn main() {
let a = ExampleStruct1 {};
let f = test(&a);
println!("{}", f);
let b = ExampleStruct2 {};
let f = test(&b);
println!("{}", f);
}6.3.2 使用trait的关联类型来约束类型
trait Person {
type AAA;
fn shout(&self, msg: Self::AAA);
}
// 定义一个结构体,规定内部成员homeowner的类型必须实现Person这个trait
// 成员 house_name 的类型 必须是 实现了Person 这个trait的类 中定义的关联类型 AAA
struct House<T: Person> {
homeowner: T,
house_name: T::AAA,
}
struct Man {
name: String,
}
impl Person for Man {
// 如果将这个 AAA 改成其他类型, 那么就会在实例化 House时house_name 也要改成这里指定的类型
type AAA = String;
fn shout(&self, msg: Self::AAA) {
println!("{}", msg);
}
}
fn main() {
let s = House {
homeowner: Man {
name: "jerry".to_string(),
},
house_name: "jerry's home".to_string(),
};
s.homeowner.shout("啊啊啊".to_string());
}6.3.3 直接指定具体类型来约束关联类型
trait Person {
type AAA;
fn shout(&self, msg: Self::AAA);
}
// 定义一个结构体
// 1. 约束了内部成员的类型必须实现Person这个trait
// 2. Person实现这个trait的代码中使用的关联类型必须是String
struct House<T: Person<AAA = String>> {
homeowner: T,
}
struct Man {
name: String,
}
// 为了实例化 House 中使用的字段 homeowner 可以用
// 我们为其实现Person trait
impl Person for Man {
// 关联类型 设置为String
// 如果将这个 AAA 改成其他类型, 那么就会在实例化 House 那里报错
// 因为这样的 Man 不是我们要的
type AAA = String;
fn shout(&self, msg: Self::AAA) {
println!("{}", msg);
}
}
fn main() {
let s = House {
homeowner: Man {
name: "jerry".to_string(),
},
};
s.homeowner.shout("啊啊啊".to_string());
}6.3.4 使用trait 约束关联类型
use std::fmt::Display;
trait Person {
type AAA: Display;
fn shout(&self, msg: Self::AAA);
}
struct House<T: Person> {
homeowner: T,
}
struct Man {
name: String,
}
impl Person for Man {
type AAA = i32; //String 也行, 必须是实现了 Display 这个trait
fn shout(& self, msg: Self::AAA) {
println!("{}", msg);
}
}
fn main() {
let s = House {
homeowner: Man {
name: "jerry".to_string(),
},
};
s.homeowner.shout(123);
}6.4 关联常量 trait
trait Country {
const HISTORY: u32 = 5000;
const INDEPENDENT: bool = true;
fn get_code(&self) -> u32 {
Self::HISTORY
}
}
struct China;
impl Country for China {
const HISTORY: u32 = 7000;
}
fn main() {
let c = China;
println!("{}", c.get_code());
println!("{:?}", China::HISTORY);
println!("{:?}", <China as Country>::HISTORY);
println!("{:?}", China::INDEPENDENT);
println!("{:?}", <China as Country>::INDEPENDENT);
}6.5 继承 trait
- 用继承这个词, 其实我觉得不太好
- 比如如果说trait A继承了B, 但是实际A并没有B的行为, 只是你在实现trait A的时候,必须实现trait B
trait Animal {
fn shout(&self);
}
// 表示如果有类型要实现 Person 这个 trait, 那么它必须也要实现 Animal这个trait
// 相当于 T: Person + Animal
trait Person: Animal {
fn speak(&self);
fn shout(&self);
}
struct Student;
impl Person for Student {
fn speak(&self) {
println!("hello...");
}
fn shout(&self) {
println!("person...");
}
}
impl Animal for Student {
fn shout(&self) {
println!("animal...");
}
}
impl Student {
// 如果没有自己实现自己的方法,
// 上面2个trait 中都有shout, main中调用 d.shout() 会报错
fn shout(&self) {
println!("类自己的方法...");
}
}
fn main() {
let d = &Student;
d.shout(); // 类自己的方法...
// <Type as Trait>:: 完全限定语法,
// 在没有参数的情况下使用Person::shout()会报错, 需要用这个语法才可以
// 使用 Student 实现Person 这个trait 时的shout 方法
<Student as Person>::shout(d); // person...
Person::shout(d); // person...
<Student as Animal>::shout(d); // animal...
Animal::shout(d); // animal...
}6.6 标记用 trait
- 这些trait 没有任何行为,里面是空的. 是给编译器用的, 会给类型打上一个标记,这些标记会影响编译器的静态检查和代码生成
- 分别是 Copy ,Send, Sync, Sized 这4个
- Sized trait
- 表示类型能确定大小
- 编译阶段能确定大小的类型,编译器就自动为其实现该trait
我们分别在其他地方详细说明
7 返回值指定trait
fn returns_summarizable() -> impl Summary {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}下面这个会报错
fn returns_summarizable(switch: bool) -> impl Summary {
if switch {
NewsArticle {
headline: String::from(
"Penguins win the Stanley Cup Championship!",
),
location: String::from("Pittsburgh, PA, USA"),
author: String::from("Iceburgh"),
content: String::from(
"The Pittsburgh Penguins once again are the best \
hockey team in the NHL.",
),
}
} else {
Tweet {
username: String::from("horse_ebooks"),
content: String::from(
"of course, as you probably already know, people",
),
reply: false,
retweet: false,
}
}
}8 运算符重载
use std::ops::Add;
#[derive(Debug, Copy, Clone, PartialEq)]
struct Point {
x: i32,
y: i32,
}
// 给Point 实现Add trait, Add trait 定义了+加法 会如何操作
impl Add for Point {
type Output = Point;
fn add(self, other: Point) -> Point {
Point {
x: self.x + other.x,
y: self.y + other.y,
}
}
}
fn main() {
assert_eq!(
Point { x: 1, y: 0 } + Point { x: 2, y: 3 },
Point { x: 3, y: 3 }
);
}我们看下Add trait的定义,发现实际带有类型参数,默认值是Self
不同类型的+ 加法重载
use std::ops::Add;
struct Millimeters(u32);
struct Meters(u32);
impl Add<Meters> for Millimeters {
type Output = Millimeters;
fn add(self, other: Meters) -> Millimeters {
Millimeters(self.0 + (other.0 * 1000))
}
}
fn main() {
let mm = Millimeters(10);
let m = Meters(1);
let sum = mm + m;
// sum is of type Millimeters
println!("sum is {}", sum.0); // 1010
// 注意这样是不行的, 顺序是有关系的
// let sum2 = m + mm;
}9 trait object
9.1 使用
我们有这样一个需求, 创建一个GUI 工具, 会遍历一个列表, 调用每个元素的draw方法
我们很快就能想到使用泛型,试试看
pub trait Draw {
fn draw(&self);
}
pub struct ScreenWithGeneric<T: Draw> {
pub components: Vec<T>,
}
impl<T> ScreenWithGeneric<T>
where
T: Draw,
{
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
pub struct Button {}
impl Draw for Button {
fn draw(&self) {
println!("Button");
}
}
struct SelectBox {}
impl Draw for SelectBox {
fn draw(&self) {
println!("SelectBox");
}
}
fn main() {
let botton = Button {};
let botton2 = Button {};
let select_box = SelectBox {};
let s = ScreenWithGeneric {
// 报错了, 类型不一致了
components: vec![botton, select_box],
};
s.run();
}结果发现行不通, 问题的关键是我们只要列表里的元素能够执行 draw 方法就行, 不管它是什么类型. 有什么办法呢? trait object 可以解决这个问题
pub trait Draw {
fn draw(&self);
}
pub struct Screen {
// Box<dyn Draw> : 定义了一个 trait object
// 表示 Box里的元素必须都实现了 Draw 这个trait 就行, 不管它是什么类型
1 pub components: Vec<Box<dyn Draw>>,
}
impl Screen {
pub fn run(&self) {
for component in self.components.iter() {
component.draw();
}
}
}
pub struct Button {}
impl Draw for Button {
fn draw(&self) {
println!("Button");
}
}
struct SelectBox {}
impl Draw for SelectBox {
fn draw(&self) {
println!("SelectBox");
}
}
fn main() {
let button = Button {};
let select_box = SelectBox {};
let s = Screen {
// 这里ok了
components: vec![Box::new(button), Box::new(select_box)],
};
s.run();
}- 1
- 使用Box 包起来很好理解, 因为实现了Draw 这个trait的类型的大小不知道,直接用指针指向,所以用Box装箱
9.2 动态派发
- 当你使用泛型函数或方法时,编译器会为每个具体使用的数据类型生成专门的代码。这种机制称为单态化(Monomorphization),单态化后的代码 会执行静态派发
// 使用trait约束泛型T,这里T必须实现了Summary trait
fn notify<T: Summary>(item: T) {
println!("Breaking news! {}", item.summarize());
}
// 编译器会为每个具体使用的数据类型生成专门的代码,这样,当调用notify(tweet)和notify(article)时,直接调用的就是针对Tweet和NewsArticle预先生成好的、类型特定的函数,这就是静态派发的过程
// 为Tweet类型特化生成的notify函数
fn notify_Tweet(item: Tweet) {
println!("Breaking news! {}", item.summarize());
}
// 为NewsArticle类型特化生成的notify函数
fn notify_NewsArticle(item: NewsArticle) {
println!("Breaking news! {}", item.summarize());
}动态派发 ,使用dyn trait会用动态派发, 无法在编译期确定你调用的是哪个方法,会在运行时确定
9.3 对象安全
只能把满足对象安全的trait 转为 trait object
- 安全的标准
- 方法返回类型不是Self
- 方法中没有泛型类型参数
10 Sized Trait
动态大小的类型 DST (Dynamically Sized Types)
- 类型可以说是规定了你如何去使用一块内存: 占用多少空间, 怎么读写,比如给你弄了写方法, 方便读写
- 在数组里, [1i32,2] 占用了8字节 [1i32,2,3] 12个字节, 这2个是不同的类型, 虽然我们都说他们是数组
let s1: str = "Hello there!";
let s2: str = "How's it going?";
// 这个就ok了, 因为这个 &str 类型的 占用空间是确定的,
// 可以说动态类型的数据的一种通用使用方式, 就是一个指针去指向它的实际数据,其他比如长度等信息
let s3: &str = "Hello there!";- 前面2行报错了, 你可能会疑惑? s1和s2的占用多少空间是很确定的样子,好像没问题?
- s1 s2 两个变量是同一个
str类型,但是看起来他们占用的空间却不一样, 这就有问题了 - rust 需要在栈上为变量分配固定大小的内存空间, 这需要在编译期就知道类型的大小, 而该str类型大小不确定
为了处理这种动态大小的类型,rust提供了Sized trait 来去确定一个类型的大小在编译器是否是已知的.
在编译期知道大小的类型, rust会为其自动的实现一个trait (Sized)
上面的代码实际上 等同与, rust 会为 每个泛型函数隐式添加 Sized trait约束
默认情况下, 泛型函数只能在编译期知道其类型大小的情况下工作。但是, 你可以使用以下特殊语法来放宽这一限制:
11 一些trait
11.1 类型转换相关
From trait 让你可以从另外一个B类型的数据,创建一个A类型的数据
let my_str = "hello";
// 我们常用到的 String::from 就是String 类型 实现了 From trait
let my_string = String::from(my_str);自定义实现From
use std::convert::From;
#[derive(Debug)]
struct Number {
value: i32,
}
impl From<i32> for Number {
fn from(item: i32) -> Self {
Number { value: item }
}
}
fn main() {
let num = Number::from(30);
println!("My number is {:?}", num);
// 如果你实现了from , 那么对方也自动实现了into
let int = 5;
// 试试删除类型说明
let num: Number = int.into();
println!("My number is {:?}", num);
}- 用于易出错的转换, 所以它的返回值是 Result 型
use std::convert::TryFrom;
use std::convert::TryInto;
#[derive(Debug, PartialEq)]
struct EvenNumber(i32);
impl TryFrom<i32> for EvenNumber {
type Error = ();
fn try_from(value: i32) -> Result<Self, Self::Error> {
if value % 2 == 0 {
Ok(EvenNumber(value))
} else {
Err(())
}
}
}
fn main() {
// TryFrom
assert_eq!(EvenNumber::try_from(8), Ok(EvenNumber(8)));
assert_eq!(EvenNumber::try_from(5), Err(()));
// TryInto
let result: Result<EvenNumber, ()> = 8i32.try_into();
assert_eq!(result, Ok(EvenNumber(8)));
let result: Result<EvenNumber, ()> = 5i32.try_into();
assert_eq!(result, Err(()));
}- 要把任何类型转换成 String, 只需要实现那个类型的 ToString trait. 然而不要直接这么做, 您应该实现fmt::Display trait,它会自动提供 ToString
use std::fmt;
struct Circle {
radius: i32
}
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 trait, 就可以用 parse 把字符串转换成目标类型
FromStr定义一个类型如何从字符串转换而来
use std::str::FromStr;
// 定义一个简单的结构体,它包含一个 u32 类型的值
struct MyNumber {
value: u32,
}
// 为 MyNumber 实现 FromStr trait
impl FromStr for MyNumber {
type Err = &'static str; // 定义错误类型为 &'static str
// from_str 方法尝试将字符串解析为 MyNumber
fn from_str(s: &str) -> Result<Self, Self::Err> {
// 尝试将字符串转换为 u32
let value = s.parse::<u32>().map_err(|_| "Parse error")?;
// 如果成功,创建并返回 MyNumber 实例
Ok(Self { value })
}
}
fn main() {
// 使用 parse::<MyNumber>() 方法将字符串转换为 MyNumber
// 如果不实现 MyNumber 类型的from_str 方法, 是不能 parse的
let number = "42".parse::<MyNumber>().unwrap();
println!("Parsed value: {}", number.value);
// 如果解析失败,会返回一个错误
if let Err(e) = "not a number".parse::<MyNumber>() {
println!("Failed to parse: {}", e);
}
let parsed: i32 = "5".parse().unwrap();
let turbo_parsed = "10".parse::<i32>().unwrap();
}11.2 打印相关
11.2.1 Display
- 实现了该trait, 类型就有了to_string方法
- 只有实现了该trait , 才能使用
println!("{}",x)打印
use std::fmt;
struct Circle {
radius: i32
}
// 实现这个trait ,就有了 to_string 方法
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 };
let c=circle.to_string();
println!("{}", c);
}11.3 index 索引操作 trait
use std::ops::{Index, IndexMut};
// 定义一个简单的 Collection 类型
struct Collection<T> {
items: Vec<T>,
}
// 为 Collection 实现 Index trait(只读操作)
impl<T> Index<usize> for Collection<T> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
&self.items[index]
}
}
// 为 Collection 实现 IndexMut trait(可变操作)
impl<T> IndexMut<usize> for Collection<T> {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.items[index]
}
}
fn main() {
// 创建一个可变的 Collection 实例
let mut collection = Collection {
items: vec![10, 20, 30, 40, 50],
};
// 使用索引操作读取元素
println!("原始第一个元素: {}", collection[0]);
println!("原始第三个元素: {}", collection[2]);
// 使用索引操作修改元素
collection[0] = 100;
collection[2] = 300;
// 再次读取修改后的元素
println!("修改后第一个元素: {}", collection[0]);
println!("修改后第三个元素: {}", collection[2]);
}12 derive 自动派生
- 设计缘由
- rust 为某些类型实现某些trait时, 代码基本是固定的,重复的,是一件麻烦的事情, 不难想到应该实现自动化处理.
- 使用方法
- 因此rust设计一个属性, 就是在你想要实现名为
XXX的trait的类型前面写#[derive(XXX)], 它可以自动为你的结构体或枚举实现某些 trait, 也就是rust 编译器会自动为你生成这些 traits 的实现代码
- 因此rust设计一个属性, 就是在你想要实现名为
- 以下是一些常用的可以通过 derive 自动实现的 trait:
- Debug: 用于格式化输出,使得结构体或枚举在调试时可以打印其字段
- Clone: 允许创建一个值的副本
- Copy: 使得类型可以按位复制,而不是移动
- PartialEq 和 Eq: 用于比较两个值是否相等
- PartialOrd 和 Ord: 用于比较两个值的顺序
- Default: 提供一个类型的默认值
- Hash: 允许类型被用作哈希表中的键
12.1 Debug
派生Debug trait后, 才能使用println!(“{:?}”)中{:?}或{:#?}这种打印方式
12.2 Default
- 一般我们也不用, 首先它相当于一个构造函数, 但是我们都会自己定义一个 名字可能是new 的 静态方法