调用可变self引用方法时的Rust生命周期错误解析
问题代码
struct CodeGenerator; struct Command<'a> { command: &'a String, } struct Expression; impl CodeGenerator { fn compile_push_arguments<'a>( &'a mut self, arguments: &'a Vec<Expression>, ) -> Vec<Command<'a>> { arguments .into_iter() .flat_map(|argument| self.compile_expression(argument)) .collect() } fn compile_expression<'a>(&'a mut self, expression: &'a Expression) -> Vec<Command<'a>> { todo!() } }
编译错误信息
error[E0495]: cannot infer an appropriate lifetime for autoref due to conflicting requirements --> src/main.rs:14:39 | 14 | .flat_map(|argument| self.compile_expression(argument)) | ^^^^^^^^^^^^^^^^^^ | note: first, the lifetime cannot outlive the lifetime `'_` as defined here... --> src/main.rs:14:23 | 14 | .flat_map(|argument| self.compile_expression(argument)) | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ note: ...so that closure can access `self` --> src/main.rs:14:34 | 14 | .flat_map(|argument| self.compile_expression(argument)) | ^^^^ note: but, the lifetime must be valid for the lifetime `'a` as defined here... --> src/main.rs:8:31 | 8 | fn compile_push_arguments<'a>( | ^^ note: ...so that the types are compatible --> src/main.rs:12:9 | 12 | / arguments 13 | | .into_iter() 14 | | .flat_map(|argument| self.compile_expression(argument)) 15 | | .collect() | |______________________^ = note: expected `Vec<Command<'a>>` found `Vec<Command<'_>>` For more information about this error, try `rustc --explain E0495`.
用户疑问
- 此处的“autoref”发生在哪里?
- 当提示“首先,生命周期不能超过此处定义的
'_生命周期”时,所指的是哪个生命周期?闭包是否会隐式创建自身的生命周期? - 提示“以保证类型兼容”时,当前类型具体存在哪些不兼容的情况?
另外发现移除self引用中的mut关键字可修复问题,但不清楚原因。
错误成因与解答
1. autoref的位置
autoref发生在调用self.compile_expression的时刻。compile_expression要求接收&'a mut self,闭包捕获的self是&'a mut CodeGenerator类型,但每次调用方法时,Rust会自动生成一个临时引用(即autoref),这个临时引用的生命周期需要被推导,而这里出现了规则冲突。
2. 关于'_生命周期
这里的'_是闭包捕获self时生成的隐式匿名生命周期。闭包会捕获&mut self,Rust需要为这个捕获的引用分配一个生命周期,'_就是这个匿名生命周期的占位符。提示里的意思是:autoref生成的临时引用不能比闭包捕获的self的生命周期更长——毕竟临时引用源于闭包捕获的self,必然不能活得更久。
3. 类型不兼容的情况
函数compile_push_arguments声明返回Vec<Command<'a>>,但实际收集到的是Vec<Command<'_>>。'a是函数参数上定义的生命周期,代表self和arguments的存活周期;而'_是闭包内部的匿名生命周期,Rust无法证明'_和'a完全等价,因此判定Command<'a>和Command<'_>是不同类型,无法匹配返回值要求。
移除mut能修复的原因
当去掉self的mut后,compile_expression的签名变为&'a self,此时Rust的借用规则更宽松:共享引用(&self)可以被多次借用,且生命周期推导逻辑更简单。闭包捕获&self后,调用compile_expression生成的autoref生命周期可以直接和'a绑定——因为共享引用没有可变引用的排他性限制,Rust能推导出这个临时引用的生命周期足够长,满足返回值Vec<Command<'a>>的要求。而可变引用(&mut self)因排他性,闭包每次调用compile_expression都会重新借用self,这个新借用的生命周期无法被证明与'a一致,最终导致推导失败。
内容的提问来源于stack exchange,提问作者dipea

