面试题答案
一键面试// 定义Draw trait,包含计算面积的方法
trait Draw {
fn area(&self) -> f64;
fn draw(&self) {
println!("Drawing a shape with area: {}", self.area());
}
}
// 定义圆形结构体
struct Circle {
radius: f64,
}
// 为圆形实现Draw trait
impl Draw for Circle {
fn area(&self) -> f64 {
std::f64::consts::PI * self.radius * self.radius
}
}
// 定义矩形结构体
struct Rectangle {
width: f64,
height: f64,
}
// 为矩形实现Draw trait
impl Draw for Rectangle {
fn area(&self) -> f64 {
self.width * self.height
}
}
// 定义绘制函数,接受任何实现了Draw trait的图形对象
fn draw_shapes<T: Draw>(shapes: &[T]) {
for shape in shapes {
shape.draw();
}
}
fn main() {
let circle = Circle { radius: 5.0 };
let rectangle = Rectangle { width: 10.0, height: 5.0 };
let shapes = &[circle, rectangle];
draw_shapes(shapes);
}
关于类型参数的生命周期约束
- 为什么需要生命周期约束:在Rust中,当函数参数或者返回值涉及到引用类型时,编译器需要明确知道这些引用的生命周期。如果没有明确的生命周期标注,编译器无法确定引用是否在其使用的地方仍然有效,可能会导致悬空引用等内存安全问题。
- 在上述代码中的体现:在
draw_shapes
函数中,参数shapes
是一个切片引用&[T]
,这里虽然没有显式写出生命周期标注,但Rust有一套隐式生命周期推断规则。在这种情况下,shapes
切片中的元素引用的生命周期与shapes
本身的生命周期相同,因为切片的生命周期决定了其内部元素引用的有效范围。如果函数参数或者返回值的引用关系更加复杂,就需要显式地标注生命周期参数,例如:
fn process_shape<'a, T: Draw + 'a>(shape: &'a T) {
// 处理shape,这里shape的生命周期是'a
}
在这个函数中,显式标注了生命周期参数'a
,并且要求类型T
在生命周期'a
内有效,这样就确保了在函数process_shape
内部使用shape
时,其引用是安全有效的。通过这种方式,Rust利用生命周期约束来确保代码的正确性和安全性,避免了常见的内存安全问题,如悬空指针、野指针等。