Trait
- 在Rust中,Trait(特质)是一种定义方法集合的机制,类似于其他编程语言中的接口(java)或抽象类(c++的虚函数)。
。Trait 告诉 Rust 编译器:
- 某种类型具有哪些并且可以与其它类型共享的功能
- Trait:抽象的定义共享行为
- Trait bounds(约束): 泛型类型参数指定为实现了特定行为的类型。Trait 与其它语言的接口 (interface) 类似,但有些区别。
定义Trait
trait MyTrait { //关键字trait(线条、特征、勾勒)
// 定义Trait的方法
fn my_method(&self);//只有方法签名,没有具体实现,实现该trait的类型必须提供具体实现
// 可选:可以在这里定义其他方法
fn my_method_1(&self)-> String {
String::from("也可以提供默认实现")
};
}
实现Trait
struct MyStruct;
impl MyTrait for MyStruct {
fn my_method(&self) {
println!("This is the implementation for my_method in MyStruct");
}
}
孤儿规则
-
可在某个类型上实现trait:
-
- 类型或者trait在当前作用域中定义的
-
无法为外部类型来实现外部的trait:
-
- 这个限制是程序属性的一部分(也就是一致性)。
-
- 更具体的说是孤儿规则:之所以这样命名是因为父类型不存在。
-
- 此规则确保其他人的代码不能破坏您的diamond,反之亦然。
-
- 如果没有这个规则,两个crate可以为同一个理性实现同一个trait,Rust就不知道该使用哪个实现了。
-
- 如果允许任意 crate 对任意类型实现任意 trait,可能会导致多个 crate 中的相同类型实现相同的 trait,这会引起冲突和不可预期的行为。
引入与使用Trait
简单示例:
// 定义一个 Trait
trait Animal {
fn make_sound(&self);
}
// 实现 Trait for struct Dog
struct Dog;
impl Animal for Dog {
fn make_sound(&self) {
println!("Woof!");
}
}
// 使用 Trait 对象
fn print_sound(a: &dyn Animal) {
a.make_sound();
}
fn main() {
let dog = Dog;
// 两种调用方法
dog.make_sound(); // 输出: Woof!
print_sound(&dog); // 输出: Woof!
}
将trait打包到库中
Trait作为参数
使用多个Trait
// 定义两个trait,一个“总结”,一个“展示”
trait Summary {
fn summarize(&self) -> String;
}
trait Display {
fn display(&self);
}
// 定义结构体 CCTV
struct CCTV {
author: String, content: String,
}
// 为CCTV实现总结功能
impl Summary for CCTV {
fn summarize(&self) -> String {
format!("{}, by {}", self.content, self.author)
}
}
// 为CCTV实现展示功能
impl Display for CCTV {
fn display(&self) {
println!("Author: {}", self.author);
println!("Content: {}", self.content);
}
}
// 定义一个函数,接受实现了 Summary 和 Display traits 的对象作为参数
pub fn notify(item: &(impl Summary + Display)) {
println!("Breaking news!");
println!("Summary: {}", item.summarize());
item.display();
}
fn main() {
let article = CCTV {
author: "Sports editor".to_string(),
content: "2024欧洲杯 聚焦绿茵盛宴!".to_string(),
};
notify(&article);
}
- 运行结果:
Breaking news!
Summary: 2024欧洲杯 聚焦绿茵盛宴!, by Sports editor
Author: Sports editor
Content: 2024欧洲杯 聚焦绿茵盛宴!
// 定义两个trait,一个“总结”,一个“展示”
trait Summary {
fn summarize(&self) -> String;
}
trait Display {
fn display(&self);
}
// 定义结构体 CCTV
struct CCTV {
author: String, content: String,
}
// 为CCTV实现总结功能
impl Summary for CCTV {
fn summarize(&self) -> String {
format!("{}, by {}", self.content, self.author)
}
}
// 为CCTV实现展示功能
impl Display for CCTV {
fn display(&self) {
println!("Author: {}", self.author);
println!("Content: {}", self.content);
}
}
// 定义Hacknews 结构体
struct Hacknews {
username: String,
content: String,
}
// 为Hacknews实现“展示功能”
impl Display for Hacknews {
fn display(&self) {
println!("Tweet by {}: {}", self.username, self.content);
}
}
// 定义一个函数,接受实现了 Summary 和 Display traits 的对象作为参数
pub fn notify(item: &(impl Summary + Display)) {
println!("Breaking news!");
println!("Summary: {}", item.summarize());
item.display();
}
fn main() {
let article = CCTV {
author: "Sports editor".to_string(),
content: "2024欧洲杯 聚焦绿茵盛宴!".to_string(),
};
let hack = Hacknews {
username: "Mako".to_string(),
content: "fast, production-grade web bundler based on Rust (makojs.dev) ! from https://makojs.dev/blog/mako-open-sourced".to_string(),
};
notify(&article);
// 需要多个trait都实现了才能运行
// notify(&hack); // 报错 the trait bound 'Tweet:summary' is not satisfied ,the trait 'summary' is implemented for 'NewsArticle'
}
泛型中使用Trait
// 定义两个Trait
trait Printable {
fn print(&self);
}
trait Drawable {
fn draw(&self);
}
// 实现这两个Trait的类型
struct Circle {
radius: f64,
}
impl Printable for Circle {
fn print(&self) {
println!("Circle with radius {}", self.radius);
}
}
impl Drawable for Circle {
fn draw(&self) {
println!("Drawing a circle with radius {}", self.radius);
}
}
// 函数接受实现了两个Trait的类型
fn print_and_draw<T>(item: &T)
where
T: Printable + Drawable,
{
item.print();
item.draw();
}
fn main() {
let c = Circle { radius: 3.5 };
c.print(); // 输出: Circle with radius 3.5
c.draw(); // 输出: Drawing a circle with radius 3.5
print_and_draw(&c);
}
trait bound形式
-
从以上的例子中可以发现。在 Rust 中,有两种主要的方式来约束泛型类型参数必须实现特定的 trait(特性):
trait bound
形式和impl Trait
语法糖形式。让我们来详细比较它们: -
Trait Bound 形式:Trait bound 形式使用
where
子句来为泛型类型参数指定 trait 约束。例如:
fn example<T>(item: &T) -> usize
where
T: Display + Clone,
{
// 函数体
}
-
这里
T: Display + Clone
表示泛型类型T
必须同时实现Display
和Clone
这两个 trait。关键点在于where
子句可以将多个 trait 约束放在一起,并且在使用泛型时可以非常清晰地看到这些约束。 -
impl Trait
语法糖形式
impl Trait
语法糖形式用于在函数签名中直接指定参数必须实现的一个或多个 trait。例如:
fn notify(item: &(impl Summary + Display)) {
// 函数体
}
- 这里
&(impl Summary + Display)
表示item
参数必须实现Summary
和Display
这两个 trait。这种写法更加简洁和紧凑,适用于函数参数较少且只有少数几个约束的情况。
Ttait作为返回类型
#[derive(Trait)]
-
在定义一些struct往往会有
#[derive(Debug)]
标签。Debug
trait 是 Rust 标准库提供的一个 trait,用于以调试输出的格式打印类型的实例。当一个结构体或枚举被标记为#[derive(Debug)]
时,Rust 编译器会自动为这个类型生成实现Debug
trait 的代码。这使得我们可以通过使用{:?}
或者println!("{:?}", value)
等方式打印这个类型的实例。#[derive(Debug)] struct MyStruct { field1: i32, field2: String, }
- 在这个例子中,
MyStruct
结构体通过#[derive(Debug)]
派生了Debug
trait。这样,我们就可以使用println!("{:?}", my_struct_instance)
来打印MyStruct
的实例。
- 在这个例子中,
-
更多相关 #[derive(Trait)]的内容可去了解一下过程宏,下面是一个例子,使得在主程序中可以使用宏
AnswerFn
来自动生成一个answer()
函数,可以按照以下步骤进行:
第一步:创建 proc-macro crate
- 在一个新目录中创建一个 Cargo 项目:
cargo new proc-macro-examples --lib
- 将
Cargo.toml
文件中的内容更新为:
[package]
name = "proc-macro-examples"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
# 没有依赖
- 更新
src/lib.rs
文件,添加 proc-macro 的实现:
#![crate_type = "proc-macro"]
extern crate proc_macro;
use proc_macro::TokenStream;
#[proc_macro_derive(AnswerFn)]
pub fn derive_answer_fn(_item: TokenStream) -> TokenStream {
"fn answer() -> u32 { 123 }".parse().unwrap()
}
第二步:创建使用宏的应用程序
接下来,创建一个使用 proc-macro crate 的 Rust 应用程序。这个应用程序将使用宏 AnswerFn
来为一个结构体自动生成 answer()
函数。
- 创建另一个新的 Rust 项目:
cargo new macro-app
- 更新
Cargo.toml
文件以依赖于我们刚刚创建的 proc-macro crate:
[package]
name = "macro-app"
version = "0.1.0"
edition = "2021"
[dependencies]
proc-macro-examples = { path = "../proc-macro-examples" }
- 更新
src/main.rs
文件,使用宏AnswerFn
:
extern crate proc_macro_examples;
use proc_macro_examples::AnswerFn;
#[derive(AnswerFn)]
struct Struct;
fn main() {
assert_eq!(123, answer());
}
编译和运行
- 先编译
proc-macro-examples
cd proc-macro-examples
cargo build
然后,可以编译并运行应用程序:
cd macro-app
cargo run
- 运行结果
PS C:\Users\kingchuxing\Documents\learning-libp2p-main\macro-app> cargo run
warning: `macro-app` (bin "macro-app") generated 1 warning
Finished `dev` profile [unoptimized + debuginfo] target(s) in 0.34s
Running `target\debug\macro-app.exe`
PS C:\Users\kingchuxing\Documents\learning-libp2p-main\macro-app>