Skip to content

Commit

Permalink
feat: add rust trait
Browse files Browse the repository at this point in the history
  • Loading branch information
valcosmos committed Oct 10, 2024
1 parent 0d81674 commit 7a729e0
Show file tree
Hide file tree
Showing 2 changed files with 298 additions and 1 deletion.
2 changes: 1 addition & 1 deletion app/tag-data.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{ "rust": 2, "frontend": 1 }
{ "rust": 3, "frontend": 1 }
297 changes: 297 additions & 0 deletions data/blog/rust-trait.mdx
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 的类型,从而实现了多态性。

0 comments on commit 7a729e0

Please sign in to comment.