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

news2024/9/30 1:39:05

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/808019.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

苍穹外卖-day09

苍穹外卖-day09 本项目学自黑马程序员的《苍穹外卖》项目&#xff0c;是瑞吉外卖的Plus版本 功能更多&#xff0c;更加丰富。 结合资料&#xff0c;和自己对学习过程中的一些看法和问题解决情况上传课件笔记 视频&#xff1a;https://www.bilibili.com/video/BV1TP411v7v6/?sp…

NumPy 基础用法详解

概要 NumPy&#xff08;Numerical Python&#xff09;是一个开源的Python库&#xff0c;用于进行科学计算和数据分析。它提供了一个多维数组&#xff08;ndarray&#xff09;对象&#xff0c;用于存储和处理大规模的数据集&#xff0c;以及各种用于操作这些数组的函数。NumPy是…

PHP使用Redis实战实录2:Redis扩展方法和PHP连接Redis的多种方案

PHP使用Redis实战实录系列 PHP使用Redis实战实录1&#xff1a;宝塔环境搭建、6379端口配置、Redis服务启动失败解决方案PHP使用Redis实战实录2&#xff1a;Redis扩展方法和PHP连接Redis的多种方案 Redis扩展方法和PHP连接Redis的多种方案 一、Redis扩展方法二、php操作Redis语…

llama2.c - 垂直领域LLM训练/推理全栈利器

llama2.c是一个极简的Llama 2 LLM全栈工具&#xff0c;非常适合用于制作面向细分市场垂直领域的大规模语言模型。 推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景。 1、简介 使用此存储库中的代码&#xff0c;你可以在 PyTorch 中从头开始训练 Llama 2 LLM 架构&#xf…

Linux文件系统中目录介绍

linux的文件系统&#xff1a; 根文件系统&#xff08;rootfs&#xff09;:fhs:文件系统目录标准 Filesystem Hierarchy Standard /boot:引导文件的存放目录&#xff1a;内核文件、引导加载文件都存放在此目录 /bin&#xff1a;共所有用户使用的基本命令&#xff0c;不能管理至…

葡萄酒质量预测 -- 机器学习项目基础篇(1)

在这里&#xff0c;我们将根据给定的特征预测葡萄酒的质量。我们使用互联网上免费提供的葡萄酒质量数据集。该数据集具有影响葡萄酒质量的基本特征。通过使用几种机器学习模型&#xff0c;我们将预测葡萄酒的质量。 导入库和数据集 Pandas是一个有用的数据处理库。用于处理数…

【Linux】带你深入理解文件系统

目录 文件系统 背景知识 磁盘结构 磁盘的存储结构 磁盘抽象(逻辑&#xff0c;虚拟)结构 BootBlock&#xff1a; Super block Data blocks inode Table BlcokBitmap inode Bitmap Group Descriptor Table 文件名和inode编号 硬链接和软链接 软链接 硬链接 取消…

Python的threading模块

为引入多线程的概念&#xff0c;下面是一个例子&#xff1a; import time, datetimestartTime datetime.datetime(2024, 1, 1, 0, 0, 0) while datetime.datetime.now() < startTime:time.sleep(1)print(Program now starting on NewYear2024) 在等待time.sleep()的循环调…

【Unity2D】Order in Layer 与Layer的区别

Order in Layer 是Unity 图形渲染的顺序&#xff0c;通过设置Order in Layer &#xff0c;可以设置同层(Layer)的物体出现顺序&#xff0c;可以默认使一种物体出现在另一种物体前方 设置一物体默认在其他物体之上不被遮挡 Layer是Unity中物体的层级&#xff0c;不同物体可以位…

vue2项目中使用svg图标

在开发项目的时候经常会用到svg矢量图,而且我们使用SVG以后&#xff0c;页面上加载的不再是图片资源, 这对页面性能来说是个很大的提升&#xff0c;而且我们SVG文件比img要小的很多&#xff0c;放在项目中几乎不占用资源。 1、安装SVG依赖插件并配置加载器和路径 npm instal…

原生求生记:揭秘UniApp的原生能力限制

文章目录 1. 样式适配问题2. 性能问题3. 原生能力限制4. 插件兼容性问题5. 第三方组件库兼容性问题6. 全局变量污染7. 调试和定位问题8. 版本兼容性问题9. 前端生态限制10. 文档和支持附录&#xff1a;「简历必备」前后端实战项目&#xff08;推荐&#xff1a;⭐️⭐️⭐️⭐️…

HDFS中namenode安全模式

HDFS中namenode安全模式 安全模式的现象探究step1step2step3step4 安全模式的概述控制进入时间和离开条件安全模式自动进入离开安全模式手动进入离开 安全模式的现象探究 step1 HDFS集群在停机状态下&#xff0c;使用hdfs -daemon命令逐个进程启动集群&#xff0c;观察现象首…

网站密码忘记了怎么办?chrome浏览器,谷歌浏览器。

有时候忘记了网站的密码&#xff0c;又不想“忘记密码”去一番折腾。如果你正好用的是 chrome 浏览器。 那么根本就没必要折腾&#xff0c;直接就能看到网站密码。 操作如下 1.在浏览器右上角点击三个小点&#xff1a; 2.点这三个点&#xff1a; 3.选择“显示密码”&#x…

Windows 10 中无法最大化任务栏中的程序

方法1&#xff1a;仅选择选项 PC 屏幕 如果您使用双显示器&#xff0c;有时这可能会发生在您的 1 台计算机已插入但您正在访问的应用程序正在另一台计算机上运行的情况下&#xff0c;因此您看不到任何选项。因此&#xff0c;请设置仅在主计算机上显示显示的 PC 屏幕选项。 第…

数字信号处理中的基本运算——累加运算

1. 累加原理 s a0 a1 a2 a3 ... a(L-1) 构成一个累加结果的输入数据为一帧数据&#xff0c;一帧数据所包含的数据个数为帧长度。 累加器的工作原理是&#xff1a;每帧数据周期性地流动&#xff0c;新的数据不断进入累加器与反馈支路相加&#xff0c;实现累加&#xff…

系统集成|第七章(笔记)

目录 第七章 范围管理7.1 项目范围管理概念7.2 主要过程7.2.1 规划范围管理7.2.2 收集需求7.2.3 定义范围7.2.4 创建工作分解结构 - WBS7.2.5 范围确认7.2.6 范围控制 上篇&#xff1a;第六章、整体管理 第七章 范围管理 7.1 项目范围管理概念 概述&#xff1a;项目范围管理就…

安装vite-plugin-svg-icons

找不到合适的图标&#xff0c;如何使用其他的svg图标&#xff1f; 安装vite-plugin-svg-icons 使用svg-icon&#xff0c;即可使用iconfont等svg图标库 安装及使用过程 一、安装依赖二、在main.js中导入文件三、在src/assets新建svg目录四、vite.config.js中进行配置五、在compo…

串口设备驱动

文章目录 一、串口简介二、Linux下串口驱动框架uart_driver 结构体uart_port 的添加与移除三、Linux下串口驱动工作流程四、Linux下串口应用开发终端工作模式多线程例程一、串口简介 串口全称叫做串行接口,通常也叫做 COM 接口,串行接口指的是数据一个一个的顺序传输,通信线…

【PCIE】completion timeout disable功能总结

在PCI Express&#xff08;PCIe&#xff09;总线上进行I/O数据交换时&#xff0c;会使用Transaction layer packets&#xff08;TLP&#xff09;来将数据从一个端点传输到另一个端点。当发生一些错误或异常情况时&#xff0c;可能会导致一个TLP包没有被正常处理&#xff0c;从而…

BOB_1.0.1靶机详解

BOB_1.0.1靶机详解 靶机下载地址&#xff1a;https://download.vulnhub.com/bob/Bob_v1.0.1.ova 这个靶机是一个相对简单的靶机&#xff0c;很快就打完了。 找到ip地址后对IP进行一个单独的扫描&#xff0c;发现ssh端口被改到25468了&#xff0c;等会儿登陆时候需要用到。 目…