一、特征:理解为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,调用的是特征对象的方法
}