Rust函数模拟工具生命周期配置问题求助
构建Rust Mock工具时的生命周期错误解决
问题
构建Rust模拟工具时,为KitchenSink trait编写Mock实现,通过MockHelper处理参数匹配与调用追踪,运行时触发「lifetime may not live long enough」错误。
错误信息
error: lifetime may not live long enough --> src/lib.rs:73:9 | 67 | impl<'a> KitchenSink for MockKitchenSink<'a> { | -- lifetime `'a` defined here ... 72 | fn convert_custom_ref_to_u32(&self, arg: &CustomStruct) -> u32 { | - let's call the lifetime of this reference `'1` 73 | self.convert_custom_ref_to_u32_calls.handle_call(arg) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ argument requires that `'1` must outlive `'a`
完整示例代码
/// Represents the expected function calls the mock will recieve, /// for a function A -> B. struct MockHelper<A, B> { fns: Vec<Box<dyn MockFunction<A, B>>>, } impl<A, B> Default for MockHelper<A, B> { fn default() -> Self { Self { fns: vec![] } } } impl<A, B> MockHelper<A, B> { /// Try calling each of the listed functions until /// one succeeds. fn handle_call(&self, arg: A) -> B where A: Copy, { for f in &self.fns { match f.try_call(arg) { Some(v) => return v, None => continue, } } panic!("No matching call") } fn add_call<T: MockFunction<A, B>>(&mut self, call: T) where T: 'static, { self.fns.push(Box::new(call)) } } /// Trait to represent an expected function call /// of the form A -> B pub trait MockFunction<A, B> { fn try_call(&self, arg: A) -> Option<B>; } /// Simple argument/response pair impl MockFunction<u32, u32> for (u32, u32) { fn try_call(&self, arg: u32) -> Option<u32> { if arg == self.0 { Some(self.1) } else { None } } } struct CustomStruct {} pub trait KitchenSink { fn convert_u32_to_u32(&self, arg: u32) -> u32; fn convert_custom_ref_to_u32(&self, arg: &CustomStruct) -> u32; } #[derive(Default)] pub struct MockKitchenSink<'a> { convert_u32_to_u32_calls: MockHelper<u32, u32>, convert_custom_ref_to_u32_calls: MockHelper<&'a CustomStruct, u32>, } impl<'a> KitchenSink for MockKitchenSink<'a> { fn convert_u32_to_u32(&self, arg: u32) -> u32 { self.convert_u32_to_u32_calls.handle_call(arg) } fn convert_custom_ref_to_u32(&self, arg: &CustomStruct) -> u32 { self.convert_custom_ref_to_u32_calls.handle_call(arg) } } pub fn main() {} #[cfg(test)] pub mod tests { use crate::{CustomStruct, KitchenSink as _, MockKitchenSink}; #[test] fn kitchen_sink_generic() { let mut mock: MockKitchenSink = MockKitchenSink::default(); mock.convert_u32_to_u32_calls.add_call((1, 4)); let arg = CustomStruct {}; assert_eq!(mock.convert_u32_to_u32(1), 4); assert_eq!(mock.convert_custom_ref_to_u32(&arg), 5); } }
问题分析
错误核心在于:MockKitchenSink<'a>的convert_custom_ref_to_u32_calls字段被定义为MockHelper<&'a CustomStruct, u32>,它要求传入的引用生命周期必须长于或等于'a。但convert_custom_ref_to_u32方法接收的引用&CustomStruct是调用时产生的临时生命周期'1,Rust无法保证'1能覆盖'a的整个生命周期,因此触发错误。
解决方案
关键修改思路
- 移除
MockKitchenSink的生命周期参数,避免将结构体生命周期与引用绑定 - 调整
MockHelper和MockFunction的定义,支持处理任意生命周期的引用参数 - 适配
MockFunction的实现,让其能正确匹配引用类型的参数
修改后的完整代码
/// Represents the expected function calls the mock will recieve, /// for a function A -> B. struct MockHelper<A, B> { fns: Vec<Box<dyn for<'r> MockFunction<&'r A, B>>>, } impl<A, B> Default for MockHelper<A, B> { fn default() -> Self { Self { fns: vec![] } } } impl<A, B> MockHelper<A, B> { /// Try calling each of the listed functions until /// one succeeds. fn handle_call(&self, arg: &A) -> B where A: PartialEq, { for f in &self.fns { match f.try_call(arg) { Some(v) => return v, None => continue, } } panic!("No matching call") } fn add_call<T>(&mut self, call: T) where T: for<'r> MockFunction<&'r A, B> + 'static, { self.fns.push(Box::new(call)) } } /// Trait to represent an expected function call /// of the form A -> B pub trait MockFunction<A, B> { fn try_call(&self, arg: A) -> Option<B>; } /// Simple argument/response pair for u32 impl MockFunction<u32, u32> for (u32, u32) { fn try_call(&self, arg: u32) -> Option<u32> { if arg == self.0 { Some(self.1) } else { None } } } /// Simple argument/response pair for &CustomStruct impl<'r> MockFunction<&'r CustomStruct, u32> for (CustomStruct, u32) { fn try_call(&self, arg: &'r CustomStruct) -> Option<u32> { // 可根据需求实现具体匹配逻辑,示例直接返回预设值 Some(self.1) } } struct CustomStruct {} pub trait KitchenSink { fn convert_u32_to_u32(&self, arg: u32) -> u32; fn convert_custom_ref_to_u32(&self, arg: &CustomStruct) -> u32; } #[derive(Default)] pub struct MockKitchenSink { convert_u32_to_u32_calls: MockHelper<u32, u32>, convert_custom_ref_to_u32_calls: MockHelper<CustomStruct, u32>, } impl KitchenSink for MockKitchenSink { fn convert_u32_to_u32(&self, arg: u32) -> u32 { self.convert_u32_to_u32_calls.handle_call(&arg) } fn convert_custom_ref_to_u32(&self, arg: &CustomStruct) -> u32 { self.convert_custom_ref_to_u32_calls.handle_call(arg) } } pub fn main() {} #[cfg(test)] pub mod tests { use crate::{CustomStruct, KitchenSink as _, MockKitchenSink}; #[test] fn kitchen_sink_generic() { let mut mock = MockKitchenSink::default(); mock.convert_u32_to_u32_calls.add_call((1, 4)); mock.convert_custom_ref_to_u32_calls.add_call((CustomStruct {}, 5)); let arg = CustomStruct {}; assert_eq!(mock.convert_u32_to_u32(1), 4); assert_eq!(mock.convert_custom_ref_to_u32(&arg), 5); } }
修改说明
MockHelper使用for<'r> MockFunction<&'r A, B>语法,确保能适配任意生命周期的引用参数- 移除
MockKitchenSink的生命周期参数,消除结构体与引用的生命周期绑定限制 - 为
&CustomStruct类型新增MockFunction实现,支持引用参数的匹配逻辑
内容的提问来源于stack exchange,提问作者Michael Anderson
相关产品推荐
相关产品推荐

