Rust中能否为trait对象实现方法链式调用?
解决方案
你完全不需要将变换方法移出trait,以下3种方案都可以实现链式调用,同时保留trait的对象安全:
方案1:为原trait增加带Sized约束的链式默认方法
Rust中trait方法如果添加了where Self: Sized约束,就不会被纳入trait对象的虚表,完全不影响对象安全。你可以在原有trait的基础上新增默认实现的链式方法:
pub trait IntoTriangle { fn triangulate(&self) -> Vec<Triangle>; // 保留原有原地修改的方法,满足对象安全要求 fn rotate(&mut self, x: f32, y: f32, z: f32); fn scale(&mut self, x: f32, y: f32, z: f32); fn translate(&mut self, x: f32, y: f32, z: f32); // 新增链式调用方法,带Sized约束不影响对象安全 fn rotated(mut self, x: f32, y: f32, z: f32) -> Self where Self: Sized { self.rotate(x, y, z); self } fn scaled(mut self, x: f32, y: f32, z: f32) -> Self where Self: Sized { self.scale(x, y, z); self } fn translated(mut self, x: f32, y: f32, z: f32) -> Self where Self: Sized { self.translate(x, y, z); self } }
使用时直接调用带后缀的链式方法即可:
let triangles = MyObjectType::new(...) .scaled(7.0, 7.0, 7.0) .rotated(0.0, 45.0, 0.0) .translated(3.0, 0.0, -30.0) .triangulate();
方案2:通过独立扩展trait实现链式调用
如果你不想修改原有trait的定义,可以单独定义扩展trait为所有实现了IntoTriangle的类型自动添加链式调用能力:
// 原有trait完全不需要修改 pub trait IntoTriangle { fn triangulate(&self) -> Vec<Triangle>; fn rotate(&mut self, x: f32, y: f32, z: f32); fn scale(&mut self, x: f32, y: f32, z: f32); fn translate(&mut self, x: f32, y: f32, z: f32); } // 定义扩展trait pub trait IntoTriangleChain: IntoTriangle + Sized { fn scale(mut self, x: f32, y: f32, z: f32) -> Self { IntoTriangle::scale(&mut self, x, y, z); self } fn rotate(mut self, x: f32, y: f32, z: f32) -> Self { IntoTriangle::rotate(&mut self, x, y, z); self } fn translate(mut self, x: f32, y: f32, z: f32) -> Self { IntoTriangle::translate(&mut self, x, y, z); self } } // 为所有符合条件的类型自动实现扩展trait impl<T: IntoTriangle + Sized> IntoTriangleChain for T {}
使用时只要引入IntoTriangleChain trait,就可以直接用你想要的链式写法,完全不需要修改原有逻辑:
let triangles = MyObjectType::new(...) .scale(7.0, 7.0, 7.0) .rotate(0.0, 45.0, 0.0) .translate(3.0, 0.0, -30.0) .triangulate();
方案3:修改原方法返回&mut Self(支持trait对象链式调用)
如果你的场景需要在trait对象上也使用链式调用,可以直接将原有变换方法的返回值改为&mut Self,这种写法完全符合对象安全要求:
pub trait IntoTriangle { fn triangulate(&self) -> Vec<Triangle>; // 修改返回值为&mut Self fn rotate(&mut self, x: f32, y: f32, z: f32) -> &mut Self; fn scale(&mut self, x: f32, y: f32, z: f32) -> &mut Self; fn translate(&mut self, x: f32, y: f32, z: f32) -> &mut Self; } // 实现示例 impl IntoTriangle for MyObjectType { fn rotate(&mut self, x: f32, y: f32, z: f32) -> &mut Self { self.rotation = [x, y, z]; self } // 其他方法实现同理 }
使用时只需要提前声明可变变量,不管是具体类型还是dyn IntoTriangle trait对象都支持链式调用:
let triangles = { let mut obj = MyObjectType::new(...); obj.scale(7.0, 7.0, 7.0) .rotate(0.0, 45.0, 0.0) .translate(3.0, 0.0, -30.0) .triangulate() }; // trait对象也可以正常链式调用 let mut dyn_obj: Box<dyn IntoTriangle> = Box::new(MyObjectType::new(...)); dyn_obj.scale(7.0, 7.0, 7.0) .rotate(0.0, 45.0, 0.0) .translate(3.0, 0.0, -30.0);
方案选择建议
- 如果只是具体类型需要链式调用,优先选方案2,侵入性最低,不需要修改原有trait定义
- 如果需要明确区分原地修改和链式调用两种场景,选方案1
- 如果需要trait对象也支持链式调用,直接选方案3
内容的提问来源于stack exchange,提问作者Valeriy Kazantsev
相关产品推荐
相关产品推荐

