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

相关推荐
嵌入式AI的盲1 小时前
数组指针和指针数组
数据结构·算法
不知所云,1 小时前
qt cmake自定义资源目录,手动加载资源(图片, qss文件)
开发语言·qt
安冬的码畜日常2 小时前
【玩转 JS 函数式编程_006】2.2 小试牛刀:用函数式编程(FP)实现事件只触发一次
开发语言·前端·javascript·函数式编程·tdd·fp·jasmine
阑梦清川2 小时前
Java继承、final/protected说明、super/this辨析
java·开发语言
PythonFun2 小时前
Python批量下载PPT模块并实现自动解压
开发语言·python·powerpoint
Death2002 小时前
Qt 6 相比 Qt 5 的主要提升与更新
开发语言·c++·qt·交互·数据可视化
机器视觉知识推荐、就业指导2 小时前
使用Qt实现实时数据动态绘制的折线图示例
开发语言·qt
快乐就好ya3 小时前
Java多线程
java·开发语言
CS_GaoMing3 小时前
Centos7 JDK 多版本管理与 Maven 构建问题和注意!
java·开发语言·maven·centos7·java多版本
Indigo_code3 小时前
【数据结构】【顺序表算法】 删除特定值
数据结构·算法