简单了解rust的trait

Rust中,trait是一种泛型机制,用于定义一组方法或属性。主要用于实现多态、定义函数和类型之间的交互以及类型系统的一部分。

多态性

trait 允许你在不同的类型之间共享行为,而不必担心具体的实现细节。这意味着你可以定义一个trait,然后让多个类型实现这个trait,从而允许这些类型以相同的方式被使用。

rust 复制代码
use std::f32::consts::PI;
fn main() {
    let c = Circle { radius: 3.0 };
    let r = Rectangle { width: 3.0, height: 2.0 };
    // 28.274334 18.849556 6 10
    println!("{} {} {} {}", c.area(), c.perimeter(), r.area(), r.perimeter() );
}

// 几何图形一般有的行为:周长和面积
trait Shape {
    // 一般是没有结构体的fn
    fn area(&self) -> f32;
    fn perimeter(&self) -> f32;
}

// 圆形
struct Circle {
    radius: f32
}

// 为圆形实现相应的Shape行为
impl Shape for Circle {
    fn area(&self) -> f32 {
        self.radius * self.radius * PI
    }
    fn perimeter(&self) -> f32{
        2.0 * self.radius * PI
    }
}
// 矩形
struct Rectangle {
    width: f32,
    height: f32,
}

// 为矩形实现相应的Shape行为
impl Shape for Rectangle {
    fn area(&self) -> f32 {
        self.width * self.height
    }
    fn perimeter(&self) -> f32 {
        (self.width + self.height) * 2.0
    }
}

类型参数

trait 可以包含类型参数,这允许你在不指定具体类型的情况下定义一个泛型 trait

上面的例子改写下,trait Shape的时候,不定义具体类型,用泛型代替

rust 复制代码
use std::f32::consts::PI;
fn main() {
    let c = Circle { radius: 3.0 };
    let r = Rectangle { width: 3.0, height: 2.0 };
    // 28.274334 18.849556 6 10
    println!("{} {} {} {}", c.area(), c.perimeter(), r.area(), r.perimeter() );
}

// 几何图形一般有的行为:周长和面积
trait Shape<T> {
    // 一般是没有结构体的fn
    fn area(&self) -> T;
    fn perimeter(&self) -> T;
}

// 圆形
struct Circle {
    radius: f32
}

// 为圆形实现相应的Shape行为
impl Shape<f32> for Circle {
    fn area(&self) -> f32 {
        self.radius * self.radius * PI
    }
    fn perimeter(&self) -> f32{
        2.0 * self.radius * PI
    }
}
// 矩形
struct Rectangle {
    width: f32,
    height: f32,
}

// 为矩形实现相应的Shape行为
impl Shape<f32> for Rectangle {
    fn area(&self) -> f32 {
        self.width * self.height
    }
    fn perimeter(&self) -> f32 {
        (self.width + self.height) * 2.0
    }
}

矩形加泛型参数的话,就会变得复杂些,需要在实现shape的时候,限制泛型

rust 复制代码
// 定义Shape trait  
trait Shape<T> {  
    // 定义计算面积的方法  
    fn area(&self) -> T;  
    // 定义计算周长的方法  
    fn perimeter(&self) -> T;  
}  
  
// 定义矩形结构体,带有泛型T用于面积和周长的类型  
struct Rectangle<T> {  
    width: T,  
    height: T,  
}  
  
// 为Rectangle结构体实现Shape trait  
impl<T> Shape<T> for Rectangle<T> where T: std::ops::Add<Output = T> + std::ops::Mul<Output = T> + Copy {  
    // 实现area方法  
    fn area(&self) -> T {  
        self.width * self.height  
    }  
  
    // 实现perimeter方法  
    fn perimeter(&self) -> T {  
        self.width + self.height + self.width + self.height
    }  
}  
  
fn main() {  
    // 创建一个Rectangle实例,使用f64作为泛型参数  
    let rect = Rectangle { width: 5.0, height: 10.0 };  
  
    // 调用Rectangle实例上的area和perimeter方法  
    println!("Area: {}", rect.area());  
    println!("Perimeter: {}", rect.perimeter());  
}

#[derive]

在Rust中,#[derive] 是一个属性,它允许你从现有的 trait 派生出 trait 实现。这样,你就可以为你的结构体自动添加一些有用的 trait 实现,比如 DefaultDebug

rust 复制代码
#[derive(Default, Debug)]
struct Point {
    x: f32,
y: f32 }

fn main() {
    let p = Point::default();
    // Point { x: 0.0, y: 0.0 }
    println!("{:?}", p);
}

#[derive(Default, Debug)] 属性告诉编译器为结构体 Point 自动添加 DefaultDebug trait 的实现。

Default trait 允许我们创建一个 Point 结构体的默认实例,而不需要显式地初始化每个字段。

Debug trait 允许我们打印 Point 结构体的实例时,使用人类可读的格式。

使用 Point::default() 创建一个 Point 结构体的默认实例。 这个调用会自动调用 Default trait 的实现,为 xy 字段分配默认值(通常是零)

使用 println! 宏来打印 Point 结构体的实例,Debug trait 的实现会自动格式化输出。

也可以自己去实现Default, Debug,这样就可以改变默认值和打印的格式。

rust 复制代码
// 定义一个结构体 `Point`,包含两个 `f32` 类型的字段 `x` 和 `y`。
struct Point {
    x: f32,
    y: f32,
}

// 为 `Point` 结构体实现 `Default` trait。
impl Default for Point {
    // `Default` trait 要求提供一个构造函数 `fn default() -> Self`。
    fn default() -> Self {
        // 创建一个 `Point` 实例,其中 `x` 和 `y` 字段都被初始化为零。
        Point { x: 1.0, y: 1.0 }
    }
}
use std::fmt;
// 为 `Point` 结构体实现 `Debug` trait。
impl fmt::Debug for Point {
    // `Debug` trait 要求提供一个格式化方法 `fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result`。
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        // 使用格式化字符串 `"{:?}"` 来打印 `Point` 实例。
        write!(f, "我是一个小可爱 Point {{ x: {}, y: {} }}", self.x, self.y)
    }
}

fn main() {
    // 使用 `Point::default()` 创建一个 `Point` 结构体的默认实例。
    let p = Point::default();
    
    // 使用 `println!` 宏来打印 `Point` 结构体的实例,`Debug` trait 的实现会自动格式化输出。
    // 我是一个小可爱 Point { x: 1, y: 1 }
    println!("{:?}", p);
}

Point 结构体提供了自己的 DefaultDebug trait 实现。Default trait 的实现提供了一个构造函数 default(),它返回一个包含默认值(在这个例子中是零)的 Point 实例。Debug trait 的实现提供了一个格式化方法 fmt(),它接受一个 Formatter 参数并返回一个 Result,表示格式化操作的结果。在这个方法中,我们使用 write! 宏来格式化 Point 实例的字段,并使用格式化字符串 "{:?}" 来打印。

同样常用的还有Copy和Clone trait,当结构体的所有字段都实现了这两trait的时候,整个结构体就可以添加其实现

rust 复制代码
#[derive(Copy, Clone, Default, Debug)]
struct Point {
    x: f32,
    y: f32,
}

fn main() {
    let p = Point::default();
    let p1 = p;
    // Point { x: 0.0, y: 0.0 } Point { x: 0.0, y: 0.0 }
    println!("{:?} {:?}", p1, p);
}

这里如果去掉Copy, Clone就会报错value borrowed here after move,因为p的所有权转移给了p1,所以不能打印p,而加了Copy,相当于p1自动复制了一份,所以不会报错。

Copy和Clone

在Rust语言中,CopyClone是两个不同的特性(traits),它们用于处理数据的复制,但这两是有区别的,能Copy的一定能Clone,但能Clone的不一定能Copy。

  1. Copy trait:
    • 当一个类型实现了Copy trait,它的值可以简单地通过位拷贝(bitwise copy)来复制。这意味着,对于实现了Copy的类型,当一个变量被赋值给另一个变量时,或者作为函数参数传递时,实际上是在做浅拷贝(shallow copy)。
    • Copy类型的实例在离开作用域时不会调用drop函数,因为它们的数据可以简单地通过复制来转移。
    • 通常,简单的标量类型(如整数、浮点数、布尔值、字符等 )和某些特定的复合类型(如元组,当且仅当它们包含的类型都实现了Copy时)会自动实现Copy trait,值在栈上。
    • 一个类型如果实现了Drop trait,则不能实现Copy trait,因为Drop用于执行清理逻辑,而Copy则意味着不需要清理。也就是Drop互斥
  2. Clone trait:
    • 当一个类型实现了Clone trait,它意味着该类型的值可以通过调用clone方法来创建一个深拷贝(deep copy)。这通常涉及到分配新的内存并复制数据,特别是当类型包含指针或引用时。
    • 实现了Clone trait的类型,其值在被复制时,会显式地调用clone方法。这允许类型自定义复制逻辑,例如,当类型包含堆分配的内存时。
    • 所有可以Copy的类型也都可以Clone,因为浅拷贝是深拷贝的一个特例。实际上,对于实现了Copy trait的类型,Rust会自动为它们实现一个默认的clone方法,该方法简单地执行位拷贝。
    • Clone trait要求类型实现一个名为clone的方法,该方法返回一个类型为Self的新实例。

总结一下,Copy trait用于简单的位拷贝,适用于不需要资源管理的类型,而Clone trait用于创建值的深拷贝,适用于需要自定义复制逻辑的类型。通常,对于需要复制的复杂类型,我们会实现Clone trait,而对于简单的标量类型,则它们通常会自动实现Copy trait。

再用下上面的例子:

rust 复制代码
// 这里去掉了Copy,不然会报错,因为String不支持Copy
#[derive(Default, Debug, Clone)]
struct Point {
    x: f32,
    y: f32,
    memo: String
}

fn main() {
    let p = Point::default();
    // 显示的调用clone方法
    let p1 = p.clone();
    // Point { x: 0.0, y: 0.0, memo: "" } Point { x: 0.0, y: 0.0, memo: "" }
    println!("{:?} {:?}", p1, p);
}

Default的使用

Default 的实战应用场景是这样的,当一个结构体的属性太多的时候,我们可以借助 Default 来填充一部分属性的默认值。

rust 复制代码
#[derive(Debug, Clone)]
struct Point {
    x: f32,
    y: f32,
    memo: String
}

// 设置一些字段的初始值
impl Default for Point {
    fn default() -> Self {
        Self {
            x: 1.0,
            y: 1.0,
            memo: "".to_string(),
        }
    }
}

impl Point {
    // new的时候 不用传所有字段
    fn new (memo: &str) -> Self {
        Point {
            memo: memo.to_string(),
            // 用default填充剩余字段,注意是两个.
            ..Self::default()
        }
    }
}
  
fn main() {
    let p = Point::new("1点");
    // Point { x: 1.0, y: 1.0, memo: "1点" } 
    println!("{:?} ",  p);
}

常见trait

Deref 和 DerefMut

在 Rust 编程语言中,DerefDerefMut 是两个非常重要的特性(traits),它们用于重载解引用操作符 *。这两个特性允许开发者自定义当一个类型的实例被解引用时应该返回什么。

Deref 的定义如下:

rust 复制代码
trait Deref {
    type Target: ?Sized;
    fn deref(&self) -> &Self::Target;
}

这里,Target 是一个关联类型,表示解引用后的类型。deref 方法返回一个指向 Target 类型的引用。

例如,Box<T> 类型实现了 Deref 特性,使得你可以像使用普通引用一样使用 Box<T>

DerefMut 特性与 Deref 类似,但是它是用于可变解引用。也就是说,它允许你自定义当一个类型的可变实例被解引用时应该返回什么。

举个例子:

rust 复制代码
// 引入标准库中的 Deref 和 DerefMut 特性
use std::ops::{Deref, DerefMut};

// 定义一个泛型结构体 MyBox,它包含一个泛型类型 T 的值
struct MyBox<T>(T);

// 为 MyBox 实现一个构造函数 new,它接受一个 T 类型的值并将其包装在 MyBox 中
impl<T> MyBox<T> {
    fn new(x: T) -> MyBox<T> {
        MyBox(x) // 创建一个新的 MyBox 实例并返回
    }
}

// 为 MyBox 实现 Deref 特性
impl<T> Deref for MyBox<T> {
    // 关联类型 Target 指定了解引用 MyBox 后应该返回的类型
    type Target = T;

    // deref 方法返回一个对 MyBox 内部值的引用
    fn deref(&self) -> &T {
        &self.0 // 返回对 MyBox 中存储值的引用
    }
}

// 为 MyBox 实现 DerefMut 特性
impl<T> DerefMut for MyBox<T> {
    // deref_mut 方法返回一个对 MyBox 内部值的可变引用
    fn deref_mut(&mut self) -> &mut T {
        &mut self.0 // 返回对 MyBox 中存储值的可变引用
    }
}

// 主函数,程序的入口点
fn main() {
    // 创建一个新的可变 MyBox 实例,其中包含值 5
    let mut x = MyBox::new(5);

    // 通过解引用操作符 * 使用 MyBox 中的值,并增加 1
    // Rust 会自动调用 deref_mut 方法来获取可变引用
    *x += 1;

    // 使用 assert_eq! 宏来检查 MyBox 中的值是否为 6
    // Rust 会自动调用 deref 方法来获取不可变引用
    assert_eq!(*x, 6); // 如果断言失败,程序会 panic
}

Drop

在 Rust 中,Drop 特性用于自定义当一个值离开作用域时应该执行的代码。这通常用于资源管理,比如关闭文件、释放内存或网络连接等。

当一个变量(非标识类型)离开作用域时,Rust 会自动调用 Drop 特性中的 drop 方法。Rust 保证每个值在其作用域结束时只调用一次 drop 方法,即使是通过 panic! 异常提前退出作用域。

下面是 Drop 特性的定义:

rust 复制代码
trait Drop {
    fn drop(&mut self);
}

举个例子:

rust 复制代码
// 定义一个结构体 CustomSmartPointer,它包含一个 String 类型的字段 data
struct CustomSmartPointer {
    data: String,
}

// 为 CustomSmartPointer 实现 Drop 特性
impl Drop for CustomSmartPointer {
    // 实现 drop 方法,该方法在 CustomSmartPointer 实例离开作用域时被自动调用
    fn drop(&mut self) {
        // 打印一条消息,说明正在丢弃 CustomSmartPointer 实例
        // 并且包含该实例的数据内容
        println!("Dropping CustomSmartPointer with data `{}`!", self.data);
    }
}

// 主函数,程序的入口点
fn main() {
    // 创建一个新的 CustomSmartPointer 实例 c,并赋予它一个 String 值 "my stuff"
    let c = CustomSmartPointer {
        data: String::from("my stuff"),
    };

    // 创建另一个新的 CustomSmartPointer 实例 d,并赋予它一个 String 值 "other stuff"
    let d = CustomSmartPointer {
        data: String::from("other stuff"),
    };

    // 打印一条消息,说明 CustomSmartPointer 实例已被创建
    println!("CustomSmartPointers created.");

    // 当 main 函数结束时,实例 c 和 d 将离开作用域
    // Rust 会自动调用它们的 drop 方法,打印出相应的丢弃消息
    // 注意:这里的 drop 调用是隐式的,由 Rust 在实例离开作用域时自动完成
}
// CustomSmartPointers created.
// Dropping CustomSmartPointer with data `other stuff`!
// Dropping CustomSmartPointer with data `my stuff`!

AsRef 和 AsMut的使用

在Rust中,AsRefAsMut是两个非常有用的特质(traits),它们提供了一种将类型转换为引用的通用方式。

AsRef特质允许将任意类型转换为引用。这意味着如果你有一个实现了AsRef<T>的类型U,你可以获取一个类型为&T的引用。

AsRef的定义如下:

php 复制代码
trait AsRef<T> {
    fn as_ref(&self) -> &T;
}

使用:

rust 复制代码
use std::path::Path;

// 定义一个泛型函数`print_path`,它接受一个实现了`AsRef<Path>`特质的参数`path`。
// 这意味着任何可以提供`Path`引用的类型都可以作为参数传递给这个函数。
fn print_path<T: AsRef<Path>>(path: T) {
    // 使用`as_ref`方法将传入的参数转换为`Path`引用。由于`T`实现了`AsRef<Path>`,
    // 因此我们可以安全地调用`as_ref`方法。
    let path_ref = path.as_ref();
    println!("Path: {:?}", path_ref);
}

fn main() {

    let string_path = "some/file/path".to_string();
    let str_path = "some/file/path";

    // 调用`print_path`函数,传递`string_path`作为参数。由于`String`实现了`AsRef<Path>`,
    // 因此它可以直接被转换为一个`Path`引用。
    print_path(string_path); // 输出: Path: "some/file/path"
    // 再次调用`print_path`函数,这次传递`str_path`作为参数。`&str`也实现了`AsRef<Path>`,
    // 因此它也可以被转换为一个`Path`引用。
    print_path(str_path); // 输出: Path: "some/file/path"
}

这样用户调用方法的时候,传的类型相对比较灵活,方便用户使用。

AsMut 顾名思义,和 AsRef 类似,但是可以得到一个值的可变引用。 如果我们在编写一些通用的库给别人用的时候,或者在项目中的公共代码给其他模块用的时候,定义函数参数的时候,建议使用 AsRef 指定参数类型,方便调用者使用。

From 和 Into

在 Rust 编程语言中,FromInto 是两个非常核心的 traits,它们用于类型之间的转换。

From trait 允许一种类型定义如何从另一种类型转换而来。它通常用于实现从其他类型到目标类型的转换逻辑。From trait 的定义如下:

rust 复制代码
trait From<T> {
    fn from(T) -> Self;
}

当你为你的类型实现了 From<T>,你就可以使用 .into() 方法或者 From::from 函数来进行转换

例如,假设我们有一个类型 MyType,并且我们想从 String 转换到 MyType,我们可以这样做:

rust 复制代码
// 使用 derive 宏为 MyType 结构体添加 Debug trait 的实现,
// 这样我们就可以使用调试格式打印 MyType 的实例。
#[derive(Debug)]
struct MyType(String);

// 为 MyType 实现 From<String> trait,这表示我们可以从 String 类型转换到 MyType 类型。
impl From<String> for MyType {
    // 实现 from 方法,它接收一个 String 类型的参数 s 并返回一个 MyType 实例。
    fn from(s: String) -> MyType {
        // 创建一个 MyType 实例,将接收到的 String 包装在 MyType 中。
        MyType(s)
    }
}

fn main() {
    // 创建一个 String 实例,包含文本 "Hello, world!"。
    let s = "Hello, world!".to_string();
    
    // 使用 From::from 方法将 String s 转换为 MyType。
    // 这实际上是调用我们上面实现的 from 方法。
    let my_type = MyType::from(s.clone());
    // 由于我们为 MyType 实现了 Debug trait,所以这里可以打印。
    println!("{:?}", my_type.0); // "Hello, world!"
    
    // 使用 .into() 方法将 String s 转换为 MyType。
    // 由于我们为 MyType 实现了 From<String>,Rust 自动为我们提供了 Into<MyType> 的实现。
    // 这行代码等价于调用 MyType::from(s),但是使用了不同的语法。
    let my_type2: MyType = s.into();
    // 再次使用调试格式打印 my_type2 实例。
    println!("{:?}", my_type2.0); // "Hello, world!"
}

Into trait 与 From 相对应,但它翻转了转换的方向。它通常用于将某个类型转换为另一个类型,而不需要显式地指定目标类型。Into trait 的定义如下:

rust 复制代码
trait Into<T> {
    fn into(self) -> T;
}

当你为类型实现了 From<T>,Rust 会自动为该类型提供 Into<T> 的实现。这就是为什么在上面的例子中,我们能够直接使用 .into() 方法将 String 转换为 MyType

  • From:定义了如何从其他类型转换为自己的类型。
  • Into:定义了如何将自己的类型转换为其他类型。

它们通常成对出现,因为实现了 From<T> 通常意味着也实现了 Into<T>。这些 traits 使得类型转换在 Rust 中变得非常灵活和方便,同时也是 Rust 强调类型安全的一个体现。通过显式地定义类型之间的转换关系,Rust 编译器可以在编译时检查转换是否合理,从而避免运行时错误。

相关推荐
m0_74825488几秒前
DataX3.0+DataX-Web部署分布式可视化ETL系统
前端·分布式·etl
求知若饥7 分钟前
NestJS 项目实战-权限管理系统开发(六)
后端·node.js·nestjs
ZJ_.12 分钟前
WPSJS:让 WPS 办公与 JavaScript 完美联动
开发语言·前端·javascript·vscode·ecmascript·wps
GIS开发特训营17 分钟前
Vue零基础教程|从前端框架到GIS开发系列课程(七)响应式系统介绍
前端·vue.js·前端框架·gis开发·webgis·三维gis
Cachel wood42 分钟前
python round四舍五入和decimal库精确四舍五入
java·linux·前端·数据库·vue.js·python·前端框架
学代码的小前端44 分钟前
0基础学前端-----CSS DAY9
前端·css
gb42152871 小时前
springboot中Jackson库和jsonpath库的区别和联系。
java·spring boot·后端
joan_851 小时前
layui表格templet图片渲染--模板字符串和字符串拼接
前端·javascript·layui
程序猿进阶1 小时前
深入解析 Spring WebFlux:原理与应用
java·开发语言·后端·spring·面试·架构·springboot