rust:特征特征对象对象安全

一、特征:理解为java里的接口就行。表示有一些能力

1、静态分配

静态分发是编译时决定的,编译器在编译时就知道对象的具体类型,并为它生成相应的代码。

Box 表示一个具体类型 T 被分配在堆上。

rust 复制代码
struct Cat;
struct Dog;

impl Cat {
    fn sound(&self) {
        println!("Meow!");
    }
}

impl Dog {
    fn sound(&self) {
        println!("Woof!");
    }
}

fn main() {
    let cat = Box::new(Cat);
    let dog = Box::new(Dog);
    
    cat.sound(); // 静态类型已知,编译时决定调用 Cat 的 sound 方法
    dog.sound(); // 静态类型已知,编译时决定调用 Dog 的 sound 方法
}

优点:生成高效的代码,因为编译器可以进行内联优化。

缺点:只能在编译时知道类型,无法在运行时选择类型。

2、动态分配

动态分发是在运行时决定的,它允许在编译时不确定具体的类型,但需要通过运行时的"类型表"来动态分发方法调用。

Box 表示一种可以实现特征 Trait 的类型,但具体是哪种类型只有在运行时才能确定

rust 复制代码
trait Animal {
    fn sound(&self);
}

struct Cat;
struct Dog;

impl Animal for Cat {
    fn sound(&self) {
        println!("Meow!");
    }
}

impl Animal for Dog {
    fn sound(&self) {
        println!("Woof!");
    }
}

fn main() {
    let cat: Box<dyn Animal> = Box::new(Cat);
    let dog: Box<dyn Animal> = Box::new(Dog);
    
    cat.sound(); // 动态分发,运行时决定调用 Cat 的 sound 方法
    dog.sound(); // 动态分发,运行时决定调用 Dog 的 sound 方法
}

优点:可以处理不同类型的对象,只要它们实现了相同的特征。支持运行时的多态。

缺点:因为要在运行时查找具体类型和方法,调用的开销会比静态分发大,而且编译器无法进行内联优化

二、特征对象

1、为什么要有特征对象

①这段代码没办法通过编译:

rust 复制代码
trait Summary {
    fn summarize(&self) -> String;
}

struct Post {
    content: String,
}

impl Summary for Post {
    fn summarize(&self) -> String {
        format!("Post: {}", self.content)
    }
}

struct Weibo {
    content: String,
}

impl Summary for Weibo {
    fn summarize(&self) -> String {
        format!("Weibo: {}", self.content)
    }
}

fn returns_summarizable(switch: bool) -> impl Summary {
    if switch {
        Post {
            content: String::from("This is a post."),
        }
    } else {
        Weibo {
            content: String::from("This is a weibo."),
        }
    }
}

fn main() {
    let item = returns_summarizable(true);
    println!("{}", item.summarize());
}

在 returns_summarizable 函数中,我们尝试返回 impl Summary,但是根据 switch 的值可能返回 Post 或 Weibo 类型。

Rust 的编译器需要在编译时确定返回的具体类型,但由于 if 和 else 分支的返回类型不相同,因此会导致类型不匹配,最终导致编译错误。

②修改方法:使用枚举:

增加新结构体,新增了 Blog 结构体,并实现了 Summary 特征。

扩展枚举,SocialMedia 枚举现在包含三个变体:Post、Weibo 和 Blog。

实现特征,SocialMedia 枚举的 summarize 方法现在可以处理三种类型的变体。

返回枚举,returns_summarizable 函数接受一个 u8 类型的 switch,根据它的值返回不同的 SocialMedia 实例。

rust 复制代码
trait Summary {
    fn summarize(&self) -> String;
}

struct Post {
    content: String,
}

impl Summary for Post {
    fn summarize(&self) -> String {
        format!("Post: {}", self.content)
    }
}

struct Weibo {
    content: String,
}

impl Summary for Weibo {
    fn summarize(&self) -> String {
        format!("Weibo: {}", self.content)
    }
}

struct Blog {
    content: String,
}

impl Summary for Blog {
    fn summarize(&self) -> String {
        format!("Blog: {}", self.content)
    }
}

// 定义一个枚举来封装 Post、Weibo 和 Blog
enum SocialMedia {
    Post(Post),
    Weibo(Weibo),
    Blog(Blog),
}

impl Summary for SocialMedia {
    fn summarize(&self) -> String {
        match self {
            SocialMedia::Post(post) => post.summarize(),
            SocialMedia::Weibo(weibo) => weibo.summarize(),
            SocialMedia::Blog(blog) => blog.summarize(),
        }
    }
}

fn returns_summarizable(switch: u8) -> SocialMedia {
    match switch {
        0 => SocialMedia::Post(Post {
            content: String::from("This is a post."),
        }),
        1 => SocialMedia::Weibo(Weibo {
            content: String::from("This is a weibo."),
        }),
        2 => SocialMedia::Blog(Blog {
            content: String::from("This is a blog."),
        }),
        _ => panic!("Invalid switch value!"),
    }
}

fn main() {
    let item = returns_summarizable(2); // 可以传入 0, 1 或 2
    println!("{}", item.summarize());
}

④新问题:假设开发一个绘图程序,可以绘制不同类型的形状,然后绘制它们。

rust 复制代码
#[derive(Debug)]
enum Shape {
    Circle { radius: f64 },
    Rectangle { width: f64, height: f64 },
}

fn main() {
    let shapes = [
        Shape::Circle { radius: 10.0 },
        Shape::Rectangle { width: 20.0, height: 15.0 },
    ];

    for shape in shapes {
        draw(shape);
    }
}

fn draw(shape: Shape) {
    match shape {
        Shape::Circle { radius } => {
            println!("Drawing a Circle with radius: {}", radius);
            // 这里可以添加更多的绘图逻辑,例如计算面积、周长等
            let area = std::f64::consts::PI * radius.powi(2);
            println!("Circle area: {}", area);
        }
        Shape::Rectangle { width, height } => {
            println!("Drawing a Rectangle with width: {}, height: {}", width, height);
            // 这里可以添加更多的绘图逻辑,例如计算面积、周长等
            let area = width * height;
            println!("Rectangle area: {}", area);
        }
    }
}

定义枚举 Shape:

Shape 枚举有两个变体:Circle 和 Rectangle。每个变体都可以存储其特有的属性。

Circle 存储半径,Rectangle 存储宽度和高度。

创建形状实例:

在 main 函数中,创建了一个包含不同形状的数组 shapes,其中包含一个圆形和一个矩形。

绘制逻辑:

draw 函数使用模式匹配判断 Shape 的类型,并根据其属性打印相应的信息。

对于每种形状,还计算并打印了其面积。

⑤对象集合在编译时并不能明确确定,上面下写法就不可以了**

Ⅰ、所有形状画出来:
正确姿势:
rust 复制代码
// 定义一个 Drawable 特征
trait Drawable {
    fn draw(&self);
    fn area(&self) -> f64; // 计算面积
}

// 定义 Circle 结构体
struct Circle {
    radius: f64,
}

// 为 Circle 实现 Drawable 特征
impl Drawable for Circle {
    fn draw(&self) {
        println!("Drawing a Circle with radius: {}", self.radius);
    }

    fn area(&self) -> f64 {
        std::f64::consts::PI * self.radius.powi(2)
    }
}

// 定义 Rectangle 结构体
struct Rectangle {
    width: f64,
    height: f64,
}

// 为 Rectangle 实现 Drawable 特征
impl Drawable for Rectangle {
    fn draw(&self) {
        println!("Drawing a Rectangle with width: {}, height: {}", self.width, self.height);
    }

    fn area(&self) -> f64 {
        self.width * self.height
    }
}

fn main() {
    // 创建一个动态分配的 Drawable 对象集合
    let mut shapes: Vec<Box<dyn Drawable>> = Vec::new();

    // 动态创建形状并添加到集合中
    shapes.push(Box::new(Circle { radius: 10.0 }));
    shapes.push(Box::new(Rectangle { width: 20.0, height: 15.0 }));

    // 遍历集合并绘制每个形状
    for shape in shapes.iter() {
        shape.draw();
        println!("Area: {}", shape.area());
    }
}
Ⅱ、传入某个形状,然后画出来。
错误姿势:定义接口的机制,它本身并不是类型的实例。不能直接将 Drawable 作为参数类型,因为它不是具体的类型。
rust 复制代码
fn draw1(x:  Drawable) {
    x.draw();
}

为什么java可以rust不可以:

Rust:

Rust 强调所有权和内存安全。在 Rust 中,特征(traits)并不直接表示具体的类型,而是用于定义行为。因此,不能直接将特征作为参数类型,而需要使用特征对象(如 &dyn Trait 或 Box),以确保明确的所有权和生命周期管理。

Java:

Java 使用引用类型(如接口)来实现多态性。接口可以直接作为方法参数,因为 Java 的垃圾回收机制自动管理内存,而不需要显式处理所有权。这使得接口可以轻松地作为参数传递,而不需要关心对象的生命周期。

正确姿势:
rust 复制代码
fn draw1(x: Box<dyn DrDrawableaw>) {
    x.draw();
}
参数类型: Box<dyn Drawable>
这是一个特征对象的智能指针,表示拥有一个动态分配的对象,该对象实现了 Drawable 特征。
所有权:
当你调用 draw1 时,传入的 Box<dyn Drawable> 将移动到函数内部,函数接收者拥有这个对象的所有权。
函数执行完后,x 会被释放,意味着它所指向的动态对象也会被销毁。


fn draw2(x: &dyn Drawable) {
    x.draw();
}

参数类型: &dyn Drawable
这是一个对实现了 Drawable 特征的对象的不可变引用。
所有权:
draw2 接收一个对 Drawable 对象的引用,因此不会转移所有权。
调用此函数不会影响传入对象的生命周期;该对象在函数外部仍然有效。

2、什么是特征对象(trait objects)

Rust 中的一种动态类型,可以用来实现多态。它允许在运行时处理实现了特定特征的不同类型,而无需知道具体的类型。特征对象通常通过 &dyn Trait 或 Box 来创建。

rust 复制代码
trait Drawable {
    fn draw(&self);
}

struct Circle;
struct Square;

impl Drawable for Circle {
    fn draw(&self) {
        println!("Drawing a circle.");
    }
}

impl Drawable for Square {
    fn draw(&self) {
        println!("Drawing a square.");
    }
}

fn draw_shape(shape: &dyn Drawable) {
    shape.draw();
}

fn main() {
    let circle = Circle;
    let square = Square;

    draw_shape(&circle); // 输出: Drawing a circle.
    draw_shape(&square); // 输出: Drawing a square.
}
在这段代码中,特征对象是 &dyn Drawable
特征(Trait):

Drawable 是一个特征,它定义了一组行为(在这个例子中是 draw 方法)。特征提供了一个接口,任何实现了该特征的类型都必须提供这些方法的具体实现。

rust

复制代码

rust 复制代码
trait Drawable {
    fn draw(&self);
}
对象体现

对象: 在 Rust 中,对象是指实现了某个特征的具体类型。在这个例子中,Circle 和 Square 是实现了 Drawable 特征的具体类型(对象)。

rust 复制代码
struct Circle;
struct Square;

impl Drawable for Circle {
    fn draw(&self) {
        println!("Drawing a circle.");
    }
}

impl Drawable for Square {
    fn draw(&self) {
        println!("Drawing a square.");
    }
}
特征对象

特征对象: &dyn Drawable 是特征对象的具体表示。它允许在运行时处理实现了 Drawable 特征的不同类型,而不需要知道具体类型是什么。通过传递 &circle 和 &square,它们被视为 &dyn Drawable 类型的特征对象。

rust 复制代码
fn draw_shape(shape: &dyn Drawable) {
    shape.draw();
}

特征: Drawable 是定义行为的特征。

对象: Circle 和 Square 是具体类型的对象,提供了特征方法的实现。

特征对象: &dyn Drawable 是指向实现了 Drawable 特征的对象的引用,允许在运行时处理不同类型的对象。

三、对象安全:

1、定义:可以通过特征对象来使用特征。特征对象允许我们在不知道具体实现类型的情况下,通过引用或指针来调用特征中的方法。Rust 规定了只有"对象安全"的特征才能成为特征对象。

如果一个特征不满足对象安全的要求,我们就不能通过 dyn Trait 来使用它。对象安全特征有以下两个关键条件:

2、特征安全,特征对象的条件:

①方法的返回类型不能是 Self:

Self 表示实现该特征的具体类型。如果一个方法返回 Self,那么使用特征对象时,编译器无法确定这个 Self 是哪个类型。

②方法不能有泛型参数:

泛型参数是在编译时确定的,但特征对象在运行时才知道具体类型,所以如果方法包含泛型参数,编译器在运行时也无法确定泛型的具体类型。

为什么不允许返回 Self 或使用泛型?

如果特征的某个方法返回 Self,特征对象就不知道具体的 Self 是哪个类型。类似地,泛型类型的具体信息在编译时决定,而特征对象需要在运行时通过虚表(vtable)进行分发,这两者在概念上冲突。

③反例

rust 复制代码
trait Cloneable {
    fn clone_self(&self) -> Self; // 返回类型是 Self
}
fn main() {
    let obj: &dyn Cloneable = ...; // 错误,Cloneable 不是对象安全的
}
编译器会报错,因为 Cloneable 的方法返回了 Self 类型,而当我们通过 dyn Cloneable 来调用这个方法时,编译器不知道 Self 是什么类型,所以无法工作

④改正:

rust 复制代码
clone_box 返回的是 Box<dyn ObjectSafeCloneable>,而不是 Self。这使得编译器知道返回的仍然是一个特征对象,且不需要知道具体类型
trait ObjectSafeCloneable {
    fn clone_box(&self) -> Box<dyn ObjectSafeCloneable>; // 返回特征对象而不是 Self
}
struct Dog;

impl ObjectSafeCloneable for Dog {
    fn clone_box(&self) -> Box<dyn ObjectSafeCloneable> {
        Box::new(Dog)
    }
}

fn main() {
    let dog = Dog;
    let obj: Box<dyn ObjectSafeCloneable> = dog.clone_box(); // OK,调用的是特征对象的方法
}
相关推荐
zwjapple7 分钟前
typescript里面正则的使用
开发语言·javascript·正则表达式
小五Five8 分钟前
TypeScript项目中Axios的封装
开发语言·前端·javascript
前端每日三省10 分钟前
面试题-TS(八):什么是装饰器(decorators)?如何在 TypeScript 中使用它们?
开发语言·前端·javascript
凡人的AI工具箱23 分钟前
15分钟学 Go 第 60 天 :综合项目展示 - 构建微服务电商平台(完整示例25000字)
开发语言·后端·微服务·架构·golang
chnming198739 分钟前
STL关联式容器之map
开发语言·c++
进击的六角龙41 分钟前
深入浅出:使用Python调用API实现智能天气预报
开发语言·python
檀越剑指大厂41 分钟前
【Python系列】浅析 Python 中的字典更新与应用场景
开发语言·python
VertexGeek42 分钟前
Rust学习(八):异常处理和宏编程:
学习·算法·rust
湫ccc1 小时前
Python简介以及解释器安装(保姆级教学)
开发语言·python
程序伍六七1 小时前
day16
开发语言·c++