// 定义一个trait
trait Animal {
fn speak(&self) -> String;
}
// 定义不同类型的结构体并实现Animal trait
struct Dog {
name: String,
}
impl Animal for Dog {
fn speak(&self) -> String {
format!("{} says Woof!", self.name)
}
}
struct Cat {
name: String,
}
impl Animal for Cat {
fn speak(&self) -> String {
format!("{} says Meow!", self.name)
}
}
// 定义函数,接受包含trait对象的Vec
fn process_animals(animals: Vec<Box<dyn Animal>>) {
for animal in animals {
let sound = animal.speak();
println!("Heard: {}", sound);
// 这里可以根据返回值进行进一步处理
if sound.contains("Woof") {
println!("It's a dog!");
} else if sound.contains("Meow") {
println!("It's a cat!");
}
}
}
fn main() {
let dog = Dog { name: "Buddy".to_string() };
let cat = Cat { name: "Whiskers".to_string() };
let animals: Vec<Box<dyn Animal>> = vec![Box::new(dog), Box::new(cat)];
process_animals(animals);
}
类型约束处理逻辑
- trait定义:定义
Animal
trait,规定了所有实现该trait的类型都必须实现speak
方法。这是对实现类型的约束,确保不同结构体类型有统一的行为接口。
- 函数参数类型约束:
process_animals
函数接受Vec<Box<dyn Animal>>
类型的参数,这要求传入的Vec
中的元素必须是实现了Animal
trait的类型,并且通过Box
指针包装成trait对象,明确了函数接受的参数类型约束。
trait对象处理逻辑
- 创建trait对象:在
main
函数中,通过Box::new
将Dog
和Cat
结构体实例包装成Box<dyn Animal>
,即trait对象。这样做是因为Rust在编译时需要知道确切的类型大小,而trait对象通过指针间接访问,可以在运行时确定具体类型,从而实现多态。
- 函数内使用trait对象:在
process_animals
函数中,通过遍历Vec<Box<dyn Animal>>
,对每个trait对象调用speak
方法。由于trait对象是基于动态分发的,所以会根据具体的对象类型(Dog
或Cat
)调用相应的speak
实现,实现了多态行为。
生命周期处理逻辑
- trait方法签名中的生命周期:
speak
方法签名为fn speak(&self) -> String
,&self
表示方法接受一个对自身的不可变引用,其生命周期与调用该方法的对象的生命周期相关联。由于返回值是String
,它拥有自己的所有权,不依赖于对象的生命周期,所以不存在生命周期冲突问题。
- 函数参数和局部变量的生命周期:
process_animals
函数的参数animals
的生命周期从函数调用开始,到函数结束。在函数内部遍历animals
时,每次取出的animal
的生命周期也与animals
相关,并且在遍历结束后就结束。由于speak
方法返回的String
有自己的所有权,所以整个过程中不存在生命周期相关的悬垂引用等问题。