Rust中对可变引用的迭代遇到的生命周期冲突问题解决

Rust中自定义一个迭代器来迭代集合的可变引用(mut reference)的时候,经常会碰到报错:

text 复制代码
error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements

今天我们就来剖析一下到底为什么会报错。

假设我们自定义一个可变迭代器来迭代&mut Vec<T>:

rust 复制代码
// 自定义的一个迭代器
// 因为迭代器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,编译器输出以下内容:

text 复制代码
   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方法修改一下:

再运行一下,控制台输出漂亮的正确信息:

text 复制代码
   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方法中的这段代码:

rust 复制代码
let result:Option<&mut T> = self.referred_vec.get_mut(self.index);
self.index+=1;
result

改成了:

rust 复制代码
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

让我们再审视一下迭代器:

rust 复制代码
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方法其实是这样的(将生命周期标出来):

rust 复制代码
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方法是这样的:

rust 复制代码
fn next<'b>(&'b mut self) -> Option<&'b mut T> 

那么就和我们定义的type Item = &'a mut T中的生命周期'a冲突了。

所以报错信息:

text 复制代码
cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements

的意思就是:编译器推断的生命周期和你给的冲突了。也就是说你定义的迭代器期望是<IterMut<'a, T>,而编译器给你推断出来的是<IterMut<'_, T>,其中'_应该就是迭代器&mut self中没标记的生命周期。

解决方法就是像上面的那样,利用Unsafe跳过编译器检查:

rust 复制代码
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

相关推荐
Flobby5297 分钟前
Go语言新手村:轻松理解变量、常量和枚举用法
开发语言·后端·golang
Eloudy34 分钟前
简明量子态密度矩阵理论知识点总结
算法·量子力学
点云SLAM34 分钟前
Eigen 中矩阵的拼接(Concatenation)与 分块(Block Access)操作使用详解和示例演示
人工智能·线性代数·算法·矩阵·eigen数学工具库·矩阵分块操作·矩阵拼接操作
nbsaas-boot1 小时前
SQL Server 窗口函数全指南(函数用法与场景)
开发语言·数据库·python·sql·sql server
东方佑1 小时前
递归推理树(RR-Tree)系统:构建认知推理的骨架结构
开发语言·r语言·r-tree
Warren981 小时前
Java Stream流的使用
java·开发语言·windows·spring boot·后端·python·硬件工程
伍哥的传说2 小时前
Radash.js 现代化JavaScript实用工具库详解 – 轻量级Lodash替代方案
开发语言·javascript·ecmascript·tree-shaking·radash.js·debounce·throttle
算法_小学生2 小时前
支持向量机(SVM)完整解析:原理 + 推导 + 核方法 + 实战
算法·机器学习·支持向量机
xidianhuihui2 小时前
go install报错: should be v0 or v1, not v2问题解决
开发语言·后端·golang
架构师沉默2 小时前
Java优雅使用Spring Boot+MQTT推送与订阅
java·开发语言·spring boot