-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
298 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1 @@ | ||
{ "rust": 2, "frontend": 1 } | ||
{ "rust": 3, "frontend": 1 } |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,297 @@ | ||
--- | ||
title: 理解rust中的trait | ||
date: 2024-10-10 | ||
tags: ['rust'] | ||
draft: false | ||
summary: '本文介绍了Rust中的trait概念,解释其作用并通过示例展示如何定义和实现trait。' | ||
--- | ||
|
||
<TOCInline toc={props.toc} exclude="Introduction" /> | ||
|
||
## 什么是trait | ||
|
||
Traits在Rust中是一个非常重要的概念,可以将其理解为TypeScript中接口(interface)的加强版。Traits定义了一组方法,这些方法描述了某种行为。任何类型都可以实现(implement)一个trait,从而获得这种行为。让我们通过一个简单的例子来理解Traits: | ||
|
||
```rust | ||
|
||
// 定义一个名为"Describable"的trait | ||
trait Describable { | ||
// 这个trait只有一个方法 | ||
fn describe(&self) -> String; | ||
} | ||
|
||
// 定义一个结构体Person | ||
struct Person { | ||
name: String, | ||
age: u32, | ||
} | ||
|
||
// 为Person实现Describable trait | ||
impl Describable for Person { | ||
fn describe(&self) -> String { | ||
format!("我叫{},今年{}岁", self.name, self.age) | ||
} | ||
} | ||
|
||
// 定义另一个结构体Car | ||
struct Car { | ||
brand: String, | ||
model: String, | ||
} | ||
|
||
// 为Car也实现Describable trait | ||
impl Describable for Car { | ||
fn describe(&self) -> String { | ||
format!("这是一辆{}牌的{}型号汽车", self.brand, self.model) | ||
} | ||
} | ||
|
||
fn main() { | ||
let person = Person { name: String::from("xx"), age: 30 }; | ||
let car = Car { brand: String::from("xxx"), model: String::from("xxx") }; | ||
|
||
println!("{}", person.describe()); | ||
println!("{}", car.describe()); | ||
} | ||
``` | ||
在这个例子中: | ||
|
||
- 我们定义了一个Describable trait,它只有一个方法describe。 | ||
|
||
- 我们创建了两个不同的结构体:Person和Car。 | ||
|
||
- 我们为这两个结构体都实现了Describable trait。这意味着它们都必须提供describe方法的具体实现。 | ||
|
||
- 在main函数中,我们可以调用这两个不同类型的describe方法。 | ||
|
||
Traits的强大之处在于它们允许我们定义共享行为,而不需要关心具体的类型。这在泛型编程中特别有用。例如: | ||
|
||
```rust | ||
fn print_description<T: Describable>(item: &T) { | ||
println!("描述: {}", item.describe()); | ||
} | ||
|
||
fn main() { | ||
let person = Person { name: String::from("李四"), age: 25 }; | ||
let car = Car { brand: String::from("本田"), model: String::from("思域") }; | ||
|
||
print_description(&person); | ||
print_description(&car); | ||
} | ||
``` | ||
在这个例子中,print_description函数可以接受任何实现了Describable trait的类型。这就是Rust中的"trait约束"。Traits还可以用于: | ||
|
||
- 默认方法实现 | ||
|
||
- 继承(通过trait继承) | ||
|
||
- 运算符重载 | ||
|
||
- 标记traits(没有任何方法的traits) | ||
|
||
总的来说,Traits是Rust中实现多态性的主要方式,它们提供了一种灵活且强大的方法来定义共享行为。对于熟悉TypeScript的程序员来说,可以将Traits理解为更强大、更灵活的接口。 | ||
|
||
## 它和 impl xxx {}有什么区别 | ||
|
||
trait 和 impl 是 Rust 中两个相关但不同的概念: | ||
|
||
trait(特征): | ||
|
||
- 定义一组方法签名,表示某种行为。 | ||
|
||
- 类似于 TypeScript 中的接口(interface)。 | ||
|
||
- 可以被多个不同的类型实现。 | ||
|
||
impl(实现): | ||
|
||
- 用于为特定类型实现方法或 trait。 | ||
|
||
- 可以直接为类型实现方法,也可以为类型实现某个 trait。 | ||
|
||
主要区别: | ||
|
||
用途: | ||
|
||
- trait 用于定义共享行为。 | ||
|
||
- impl 用于实现具体行为。 | ||
|
||
关系: | ||
|
||
- trait 定义了"什么"(接口)。 | ||
|
||
- impl 定义了"如何"(实现)。 | ||
|
||
独立性: | ||
|
||
- impl 可以独立使用,为类型直接添加方法。 | ||
|
||
- trait 通常需要 impl 来为具体类型实现其定义的行为。 | ||
|
||
让我们通过一个例子来说明这些区别: | ||
|
||
```rust | ||
// 定义一个 trait | ||
trait Greetable { | ||
fn greet(&self) -> String; | ||
} | ||
|
||
// 定义一个结构体 | ||
struct Person { | ||
name: String, | ||
} | ||
|
||
// 为 Person 直接实现方法(不使用 trait) | ||
impl Person { | ||
fn new(name: String) -> Self { | ||
Person { name } | ||
} | ||
|
||
fn get_name(&self) -> &str { | ||
&self.name | ||
} | ||
} | ||
|
||
// 为 Person 实现 Greetable trait | ||
impl Greetable for Person { | ||
fn greet(&self) -> String { | ||
format!("你好,我是 {}!", self.name) | ||
} | ||
} | ||
|
||
fn main() { | ||
let person = Person::new(String::from("xxx")); | ||
|
||
// 调用直接实现的方法 | ||
println!("名字: {}", person.get_name()); | ||
|
||
// 调用通过 trait 实现的方法 | ||
println!("{}", person.greet()); | ||
} | ||
``` | ||
|
||
在这个例子中: | ||
|
||
- trait Greetable `{...}` 定义了一个特征,指定了 greet 方法的签名。 | ||
|
||
- impl Person `{ ... }`直接为 Person 结构体实现了 new 和 get_name 方法。这种实现不涉及任何 trait。 | ||
|
||
- impl Greetable for Person `{ ... }` 为 Person 实现了 Greetable trait,提供了 greet 方法的具体实现。 | ||
|
||
关键点: | ||
|
||
- impl 可以单独使用,为类型直接添加方法(如 new 和 get_name)。 | ||
|
||
- trait 定义了一组行为(如 Greetable),但需要通过 impl Trait for Type 语法为具体类型实现。 | ||
|
||
- 一个类型可以有多个 impl 块,既可以直接实现方法,也可以实现多个不同的 traits。 | ||
|
||
总结: | ||
|
||
- trait 是定义共享行为的方式。 | ||
|
||
- impl 是实现行为的方式,既可以直接为类型实现方法,也可以为类型实现 trait。 | ||
|
||
理解这两个概念及其关系对于掌握 Rust 的面向对象编程和多态性至关重要。 | ||
|
||
## 时候用impl什么时候用trait | ||
假设我们正在开发一个简单的图形应用程序,其中有不同的形状(如圆形和矩形),我们希望为这些形状计算面积和周长。 | ||
|
||
### 使用 impl 的场景 | ||
|
||
当你只需要为一个特定类型定义方法时,可以直接使用 impl。例如,为 Circle 和 Rectangle 定义特定的方法: | ||
|
||
```rust | ||
struct Circle { | ||
radius: f64, | ||
} | ||
|
||
impl Circle { | ||
fn area(&self) -> f64 { | ||
std::f64::consts::PI * self.radius * self.radius | ||
} | ||
|
||
fn circumference(&self) -> f64 { | ||
2.0 * std::f64::consts::PI * self.radius | ||
} | ||
} | ||
|
||
struct Rectangle { | ||
width: f64, | ||
height: f64, | ||
} | ||
|
||
impl Rectangle { | ||
fn area(&self) -> f64 { | ||
self.width * self.height | ||
} | ||
|
||
fn perimeter(&self) -> f64 { | ||
2.0 * (self.width + self.height) | ||
} | ||
} | ||
``` | ||
|
||
在这里,我们为 Circle 和 Rectangle 直接实现了各自的 area 和 circumference(或 perimeter)方法。这种方式适用于每个类型都有自己特定的行为实现。 | ||
|
||
### 使用 trait 的场景 | ||
|
||
当你希望定义一组共享行为,并让多个类型实现这些行为时,使用 trait 是更好的选择。例如,定义一个 Shape trait 来统一 area 和 perimeter 的接口: | ||
|
||
```rust | ||
trait Shape { | ||
fn area(&self) -> f64; | ||
fn perimeter(&self) -> f64; | ||
} | ||
|
||
impl Shape for Circle { | ||
fn area(&self) -> f64 { | ||
std::f64::consts::PI * self.radius * self.radius | ||
} | ||
|
||
fn perimeter(&self) -> f64 { | ||
2.0 * std::f64::consts::PI * self.radius | ||
} | ||
} | ||
|
||
impl Shape for Rectangle { | ||
fn area(&self) -> f64 { | ||
self.width * self.height | ||
} | ||
|
||
fn perimeter(&self) -> f64 { | ||
2.0 * (self.width + self.height) | ||
} | ||
} | ||
``` | ||
在这个例子中: | ||
|
||
- trait Shape 定义了 area 和 perimeter 方法的接口。 | ||
|
||
- impl Shape for Circle 和 impl Shape for Rectangle 为 Circle 和 Rectangle 实现了 Shape trait。 | ||
|
||
### 何时使用 impl 和 trait | ||
|
||
- 使用 impl:当你只需要为一个特定类型定义方法,而不需要与其他类型共享这些方法时。 | ||
|
||
- 使用 trait:当你希望定义一组共享行为,并让多个类型实现这些行为时。trait 提供了一种统一的接口,使得不同类型可以通过相同的方式进行操作。 | ||
|
||
通过使用 trait,你可以编写更通用的代码。例如,你可以编写一个函数来处理任何实现了 Shape trait 的类型: | ||
|
||
```rust | ||
fn print_shape_info(shape: &impl Shape) { | ||
println!("Area: {}", shape.area()); | ||
println!("Perimeter: {}", shape.perimeter()); | ||
} | ||
|
||
fn main() { | ||
let circle = Circle { radius: 5.0 }; | ||
let rectangle = Rectangle { width: 4.0, height: 6.0 }; | ||
|
||
print_shape_info(&circle); | ||
print_shape_info(&rectangle); | ||
} | ||
``` | ||
|
||
在这个例子中,print_shape_info 函数可以接受任何实现了 Shape trait 的类型,从而实现了多态性。 |