0


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

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

1、静态分配

静态分发是编译时决定的,编译器在编译时就知道对象的具体类型,并为它生成相应的代码。
Box 表示一个具体类型 T 被分配在堆上。

structCat;structDog;implCat{fnsound(&self){println!("Meow!");}}implDog{fnsound(&self){println!("Woof!");}}fnmain(){let cat =Box::new(Cat);let dog =Box::new(Dog);
    
    cat.sound();// 静态类型已知,编译时决定调用 Cat 的 sound 方法
    dog.sound();// 静态类型已知,编译时决定调用 Dog 的 sound 方法}

优点:生成高效的代码,因为编译器可以进行内联优化。
缺点:只能在编译时知道类型,无法在运行时选择类型。

2、动态分配

动态分发是在运行时决定的,它允许在编译时不确定具体的类型,但需要通过运行时的“类型表”来动态分发方法调用。
Box 表示一种可以实现特征 Trait 的类型,但具体是哪种类型只有在运行时才能确定

traitAnimal{fnsound(&self);}structCat;structDog;implAnimalforCat{fnsound(&self){println!("Meow!");}}implAnimalforDog{fnsound(&self){println!("Woof!");}}fnmain(){let cat:Box<dynAnimal>=Box::new(Cat);let dog:Box<dynAnimal>=Box::new(Dog);
    
    cat.sound();// 动态分发,运行时决定调用 Cat 的 sound 方法
    dog.sound();// 动态分发,运行时决定调用 Dog 的 sound 方法}

优点:可以处理不同类型的对象,只要它们实现了相同的特征。支持运行时的多态。
缺点:因为要在运行时查找具体类型和方法,调用的开销会比静态分发大,而且编译器无法进行内联优化

二、特征对象

1、为什么要有特征对象

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

traitSummary{fnsummarize(&self)->String;}structPost{
    content:String,}implSummaryforPost{fnsummarize(&self)->String{format!("Post: {}",self.content)}}structWeibo{
    content:String,}implSummaryforWeibo{fnsummarize(&self)->String{format!("Weibo: {}",self.content)}}fnreturns_summarizable(switch:bool)->implSummary{if switch {Post{
            content:String::from("This is a post."),}}else{Weibo{
            content:String::from("This is a weibo."),}}}fnmain(){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 实例。

traitSummary{fnsummarize(&self)->String;}structPost{
    content:String,}implSummaryforPost{fnsummarize(&self)->String{format!("Post: {}",self.content)}}structWeibo{
    content:String,}implSummaryforWeibo{fnsummarize(&self)->String{format!("Weibo: {}",self.content)}}structBlog{
    content:String,}implSummaryforBlog{fnsummarize(&self)->String{format!("Blog: {}",self.content)}}// 定义一个枚举来封装 Post、Weibo 和 BlogenumSocialMedia{Post(Post),Weibo(Weibo),Blog(Blog),}implSummaryforSocialMedia{fnsummarize(&self)->String{matchself{SocialMedia::Post(post)=> post.summarize(),SocialMedia::Weibo(weibo)=> weibo.summarize(),SocialMedia::Blog(blog)=> blog.summarize(),}}}fnreturns_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!"),}}fnmain(){let item =returns_summarizable(2);// 可以传入 0, 1 或 2println!("{}", item.summarize());}

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

#[derive(Debug)]enumShape{Circle{ radius:f64},Rectangle{ width:f64, height:f64},}fnmain(){let shapes =[Shape::Circle{ radius:10.0},Shape::Rectangle{ width:20.0, height:15.0},];for shape in shapes {draw(shape);}}fndraw(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 的类型,并根据其属性打印相应的信息。
对于每种形状,还计算并打印了其面积。

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

Ⅰ、所有形状画出来:
正确姿势:
// 定义一个 Drawable 特征traitDrawable{fndraw(&self);fnarea(&self)->f64;// 计算面积}// 定义 Circle 结构体structCircle{
    radius:f64,}// 为 Circle 实现 Drawable 特征implDrawableforCircle{fndraw(&self){println!("Drawing a Circle with radius: {}",self.radius);}fnarea(&self)->f64{std::f64::consts::PI*self.radius.powi(2)}}// 定义 Rectangle 结构体structRectangle{
    width:f64,
    height:f64,}// 为 Rectangle 实现 Drawable 特征implDrawableforRectangle{fndraw(&self){println!("Drawing a Rectangle with width: {}, height: {}",self.width,self.height);}fnarea(&self)->f64{self.width *self.height
    }}fnmain(){// 创建一个动态分配的 Drawable 对象集合letmut shapes:Vec<Box<dynDrawable>>=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 作为参数类型,因为它不是具体的类型。
fndraw1(x:Drawable){
    x.draw();}

为什么java可以rust不可以:

Rust:
Rust 强调所有权和内存安全。在 Rust 中,特征(traits)并不直接表示具体的类型,而是用于定义行为。因此,不能直接将特征作为参数类型,而需要使用特征对象(如 &dyn Trait 或 Box),以确保明确的所有权和生命周期管理。
Java:
Java 使用引用类型(如接口)来实现多态性。接口可以直接作为方法参数,因为 Java 的垃圾回收机制自动管理内存,而不需要显式处理所有权。这使得接口可以轻松地作为参数传递,而不需要关心对象的生命周期。

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

fndraw2(x:&dynDrawable){
    x.draw();}

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

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

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

traitDrawable{fndraw(&self);}structCircle;structSquare;implDrawableforCircle{fndraw(&self){println!("Drawing a circle.");}}implDrawableforSquare{fndraw(&self){println!("Drawing a square.");}}fndraw_shape(shape:&dynDrawable){
    shape.draw();}fnmain(){let circle =Circle;let square =Square;draw_shape(&circle);// 输出: Drawing a circle.draw_shape(&square);// 输出: Drawing a square.}
在这段代码中,特征对象是 &dyn Drawable
特征(Trait):

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

traitDrawable{fndraw(&self);}
对象体现

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

structCircle;structSquare;implDrawableforCircle{fndraw(&self){println!("Drawing a circle.");}}implDrawableforSquare{fndraw(&self){println!("Drawing a square.");}}
特征对象

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

fndraw_shape(shape:&dynDrawable){
    shape.draw();}

特征: Drawable 是定义行为的特征。
对象: Circle 和 Square 是具体类型的对象,提供了特征方法的实现。
特征对象: &dyn Drawable 是指向实现了 Drawable 特征的对象的引用,允许在运行时处理不同类型的对象。

三、对象安全:

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

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

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

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

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

②方法不能有泛型参数:

泛型参数是在编译时确定的,但特征对象在运行时才知道具体类型,所以如果方法包含泛型参数,编译器在运行时也无法确定泛型的具体类型。
为什么不允许返回 Self 或使用泛型?
如果特征的某个方法返回 Self,特征对象就不知道具体的 Self 是哪个类型。类似地,泛型类型的具体信息在编译时决定,而特征对象需要在运行时通过虚表(vtable)进行分发,这两者在概念上冲突。

③反例

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

④改正:

clone_box 返回的是 Box<dynObjectSafeCloneable>,而不是 Self。这使得编译器知道返回的仍然是一个特征对象,且不需要知道具体类型
traitObjectSafeCloneable{fnclone_box(&self)->Box<dynObjectSafeCloneable>;// 返回特征对象而不是 Self}structDog;implObjectSafeCloneableforDog{fnclone_box(&self)->Box<dynObjectSafeCloneable>{Box::new(Dog)}}fnmain(){let dog =Dog;let obj:Box<dynObjectSafeCloneable>= dog.clone_box();// OK,调用的是特征对象的方法}

本文转载自: https://blog.csdn.net/w1234567465/article/details/143161266
版权归原作者 海滩超人 所有, 如有侵权,请联系我们删除。

“rust:特征特征对象对象安全”的评论:

还没有评论