一、特征:理解为java里的接口就行。表示有一些能力
1、静态分配
静态分发是编译时决定的,编译器在编译时就知道对象的具体类型,并为它生成相应的代码。
Box 表示一个具体类型 T 被分配在堆上。
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 的类型,但具体是哪种类型只有在运行时才能确定
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、为什么要有特征对象
①这段代码没办法通过编译:
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 实例。
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 或 2println!("{}", item.summarize());
}
④新问题:假设开发一个绘图程序,可以绘制不同类型的形状,然后绘制它们。
#[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 的类型,并根据其属性打印相应的信息。
对于每种形状,还计算并打印了其面积。
⑤对象集合在编译时并不能明确确定,上面下写法就不可以了**
Ⅰ、所有形状画出来:
正确姿势:
// 定义一个 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 作为参数类型,因为它不是具体的类型。
fn draw1(x: Drawable) {x.draw();
}
为什么java可以rust不可以:
Rust:
Rust 强调所有权和内存安全。在 Rust 中,特征(traits)并不直接表示具体的类型,而是用于定义行为。因此,不能直接将特征作为参数类型,而需要使用特征对象(如 &dyn Trait 或 Box),以确保明确的所有权和生命周期管理。
Java:
Java 使用引用类型(如接口)来实现多态性。接口可以直接作为方法参数,因为 Java 的垃圾回收机制自动管理内存,而不需要显式处理所有权。这使得接口可以轻松地作为参数传递,而不需要关心对象的生命周期。
正确姿势:
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 来创建。
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
复制代码
trait Drawable {fn draw(&self);
}
对象体现
对象: 在 Rust 中,对象是指实现了某个特征的具体类型。在这个例子中,Circle 和 Square 是实现了 Drawable 特征的具体类型(对象)。
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 类型的特征对象。
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)进行分发,这两者在概念上冲突。
③反例
trait Cloneable {fn clone_self(&self) -> Self; // 返回类型是 Self
}
fn main() {let obj: &dyn Cloneable = ...; // 错误,Cloneable 不是对象安全的
}
编译器会报错,因为 Cloneable 的方法返回了 Self 类型,而当我们通过 dyn Cloneable 来调用这个方法时,编译器不知道 Self 是什么类型,所以无法工作
④改正:
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,调用的是特征对象的方法
}