Rust中自定义一个迭代器来迭代集合的可变引用(mut reference)的时候,经常会碰到报错:
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
今天我们就来剖析一下到底为什么会报错。
假设我们自定义一个可变迭代器来迭代&mut Vec<T>
:
// 自定义的一个迭代器
// 因为迭代器struct中保存了Vec的引用,且Vec是支持范型的,所以我们的struct的签名中
// 必须得包含T,'a。'a表示Vec的引用的生命周期,T表示Vec中元素的类型
pub struct IterMut<'a, T> {
// 保存迭代进度的index
index: usize,
// 被迭代的集合的可变引用,因为我们是可变迭代,后续会修改Vec中的元素,所以需要mut引用
referred_vec: &'a mut Vec<T>,
}
// 为可变迭代器定义方法,主要是new方法,用于创建可变迭代器
impl<'a, T> IterMut<'a, T> {
pub fn new(vec: &'a mut Vec<T>) -> Self {
IterMut {
index: 0,
referred_vec: vec,
}
}
}
// 为我们自定义的IterMut实现标准库中的Iterator trait
impl<'a, T> Iterator for IterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.referred_vec.len() {
let result:Option<&mut T> = self.referred_vec.get_mut(self.index);
self.index+=1;
result
} else {
None
}
}
}
fn main() {
// 创建一个mut的vec,绑定到Vec实例上。因为我们后续要对vec元素进行可变迭代,所以vec必须是mut的
let mut vec = vec![1, 2, 3, 4, 5];
println!("[before]vec is:{:?}", vec);
// 创建一个可变迭代器
let iterator = IterMut::new(&mut vec);
// item是&mut i32类型的,所以要改变它的值,必须得通过*解引用
for item in iterator {
if *item % 2 == 0 {
// 将偶数都乘以2
*item *= 2;
}
}
println!("[after]vec is:{:?}", vec);
}
很朴实无华的程序对不对?
运行cargo run
,编译器输出以下内容:
Compiling rust-learining v0.1.0 (/Users/dufeng/CLionProjects/rust-learining)
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
--> src/main.rs:26:41
|
26 | let result:Option<&mut T> = self.referred_vec.get_mut(self.index);
| ^^^^^^^^^^^^^^^^^
|
note: first, the lifetime cannot outlive the anonymous lifetime defined here...
--> src/main.rs:24:13
|
24 | fn next(&mut self) -> Option<Self::Item> {
| ^^^^^^^^^
note: ...so that reference does not outlive borrowed content
--> src/main.rs:26:41
|
26 | let result:Option<&mut T> = self.referred_vec.get_mut(self.index);
| ^^^^^^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime `'a` as defined here...
--> src/main.rs:22:6
|
22 | impl<'a, T> Iterator for IterMut<'a, T> {
| ^^
note: ...so that the types are compatible
--> src/main.rs:24:46
|
24 | fn next(&mut self) -> Option<Self::Item> {
| ______________________________________________^
25 | | if self.index < self.referred_vec.len() {
26 | | let result:Option<&mut T> = self.referred_vec.get_mut(self.index);
27 | | self.index+=1;
... |
31 | | }
32 | | }
| |_____^
= note: expected `<IterMut<'a, T> as Iterator>`
found `<IterMut<'_, T> as Iterator>`
For more information about this error, try `rustc --explain E0495`.
error: could not compile `rust-learining` due to previous error
真叫人头大,不是吗?
都说Rust新手可以在编译器的帮忙下写出正确的程序,但是编译器给了我提示,我还是看不懂,无从下手,抓狂,好在在网上找到了原因,一个stackoverflow上的回答,链接在参考资料中。
让我们先按照网上的解决方法解决一下,将迭代器的next方法修改一下:
再运行一下,控制台输出漂亮的正确信息:
Compiling rust-learining v0.1.0 (/Users/dufeng/CLionProjects/rust-learining)
Finished dev [unoptimized + debuginfo] target(s) in 0.79s
Running `target/debug/rust-learining`
[before]vec is:[1, 2, 3, 4, 5]
[after]vec is:[1, 4, 3, 8, 5]
也就是说我们将next方法中的这段代码:
let result:Option<&mut T> = self.referred_vec.get_mut(self.index);
self.index+=1;
result
改成了:
let index = self.index;
self.index += 1;
let ptr = self.referred_vec.as_mut_ptr();
Some(unsafe { &mut *(ptr.add(index)) })
Why?
因为我们的迭代器其实就是个中转站,它将&mut Vec<T>
中的元素的可变引用&mut T
给我们一一找出来,然后我们就可以直接对&mut T
进行操作了,只要一开始定义的Vec还没被回收,我们就可以操作它的元素的可变引用,不再需要迭代器了,迭代器只不过是给我们提供了方便的next方法,用于从&mut Vec<T>
中拿出一系列的&mut T
。
让我们再审视一下迭代器:
impl<'a, T> Iterator for IterMut<'a, T> {
type Item = &'a mut T;
fn next(&mut self) -> Option<Self::Item> {
if self.index < self.referred_vec.len() {
let result:Option<&mut T> = self.referred_vec.get_mut(self.index);
self.index+=1;
result
} else {
None
}
}
}
从编译器角度,next方法其实是这样的(将生命周期标出来):
fn next<'a,'b>(&'b mut self) -> Option<&'a mut T>
因为next方法入参的self是迭代器,迭代器引用有自己的生命周期’b,和&mut Vec<T>
的生命周期’a没关系。
Rust编译器在方法入参只有一个引用的时候,会将入参的生命周期应用到输出上(方法中返回的result也和self有关不是吗,也就是说编译器会认为方法返回的result不应该超过迭代器&mut self
的生命周期),所以,它希望next方法是这样的:
fn next<'b>(&'b mut self) -> Option<&'b mut T>
那么就和我们定义的type Item = &'a mut T
中的生命周期’a冲突了。
所以报错信息:
cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
的意思就是:编译器推断的生命周期和你给的冲突了。也就是说你定义的迭代器期望是<IterMut<'a, T>
,而编译器给你推断出来的是<IterMut<'_, T>
,其中'_
应该就是迭代器&mut self
中没标记的生命周期。
解决方法就是像上面的那样,利用Unsafe跳过编译器检查:
let index = self.index;
self.index += 1;
let ptr = self.referred_vec.as_mut_ptr();
Some(unsafe { &mut *(ptr.add(index)) })
这儿将vec引用转成指针,其实就是将vec第0个元素的地址作为指针返回,而且指针是有类型的,对指针加上index个步长(偏移量),会被编译器乘以T类型所占字节数的。就像在C语言中的指针操作的感觉,最后我们再将其转换成可变引用返回。
参考资料:
1.https://stackoverflow.com/questions/62361624/lifetime-parameter-problem-in-custom-iterator-over-mutable-references/62363335#62363335