迭代器的生命周期混乱

问题描述 投票:0回答:2

我是 Rust 新手,目前一直在关注 使用太多链接列表学习 Rust 示例。在第

IterMut
节之前,一切对我来说都是有意义的。然而,当实现
IterMut
(以与教程不同的方式)时,我对 Rust 中的生命周期机制感到完全困惑。情况如下,首先我只定义要实现的堆栈:

pub struct List<T> {
    head: Link<T>,
}

type Link<T> = Option<Box<Node<T>>>;

struct Node<T> {
    elem: T,
    next: Link<T>,
}

好吧,当我尝试通过以下方式实现迭代器时:

pub struct IterMut<'a, T>{
    this: &'a mut Link<T>
}

impl<T> List<T> {
    pub fn iter_mut(&mut self) -> IterMut<T> {
        IterMut {
            this: &mut self.head
        }
    }
}

impl<'a, T> Iterator for IterMut<'a, T>{
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item> {
        if let Some(node) = self.this {
            Some(&mut node.elem)
        } else {
            None
        }
    }
}

无法编译,结果显示:

error: lifetime may not live long enough
impl<'a, T> Iterator for IterMut<'a, T>{
    -- lifetime `'a` defined here
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item> {
            - let's call the lifetime of this reference `'1`
        if let Some(node) = self.this {
            Some(&mut node.elem)
            ^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'1` must outlive `'a`

最初,我以这种方式实现函数

next
,使用
as_mut
map

// function body in the fn next
self.this.as_mut().map(|node|{
    self.this = &mut node.next;
    &mut node.elem
})

这也会触发编译错误(不兼容的生命周期)。

因此我想知道这里发生了什么,

self
中的
fn next
不应该与
IterMut
('a)具有相同的寿命吗?对于这种情况有什么解决办法吗?

rust lifetime
2个回答
2
投票

主要问题在于试图引用整体

head
。当该引用存在时,您无法分发对其内部任何内容的可变引用。不过,您只需要访问
Node
内的
head
即可。因此,首先,我们重构
IterMut
以仅保留对任何给定
Node
的引用:

pub struct IterMut<'a, T>{
    this: Option<&'a mut Node<T>>
}

现在,为了从

head
中得到它,我们使用
as_deref_mut()
提供的便捷方法
Option
。它只是为我们提供了对其内部内容的可变引用(如果有的话):

impl<T> List<T> {
    pub fn iter_mut(&mut self) -> IterMut<'_, T> {
        IterMut {
            this: self.head.as_deref_mut(),
        }
    }
}

现在,

'a
仅与
Node
相关联,我们可以用它做我们想做的事情,比如
take
ing它:

impl<'a, T> Iterator for IterMut<'a, T>{
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item> {
        if let Some(node) = self.this.take() {
            self.this = node.next.as_deref_mut();
            Some(&mut node.elem)
        } else {
            None
        }
    }
}

我们可以通过一个简单的

map
调用来简化它:

impl<'a, T> Iterator for IterMut<'a, T>{
    type Item = &'a mut T;
    fn next(&mut self) -> Option<Self::Item> {
        self.this.take().map(|node| {
            self.this = node.next.as_deref_mut();
            &mut node.elem
        })
    }
}

0
投票

它与“获取整个头的引用”或“多个可变引用”无关(本例中从未发生过),但与返回值的生命周期有关。

1.简单的演示代码来重现错误

首先构造一个以

&self
&mut self
作为输入,以
&'a SOMETHING
作为返回值的函数,它不是迭代器,但具有相同的效果。

#[derive(Debug)]
struct Foo<'a> {
    r_m_s: &'a mut String, // non-Copy
    r_i_s: &'a String, // Copy
}

impl<'a> Foo<'a> {
  fn test_return_ref_field(&self) -> &'a String {
    self.r_i_s // Copy, no lifetime problem
  }
  
  fn test_return_ref_mut_field(&mut self) -> &'a mut String {
    //self.r_m_s
    todo!()
  }
}

fn main() {
}

要重现错误消息,请将

test_return_ref_mut_field
方法更改为:

fn test_return_ref_mut_field(&mut self) -> &'a mut String {
    self.r_m_s
}

错误消息如下所示:

error: lifetime may not live long enough
  --> tt.rs:34:6
   |
26 | impl<'a> Foo<'a> {
   |      -- lifetime `'a` defined here
...
31 |   fn test_return_ref_mut_field(&mut self) -> &'a mut String {
   |                                - let's call the lifetime of this reference `'1`
...
34 |      self.r_m_s
   |      ^^^^^^^^^^ method was supposed to return data with lifetime `'a` but it is returning data with lifetime `'1`

error: aborting due to 1 previous error

根据错误消息,

self.r_m_s
的生命周期与输入参数
&mut self
'相同。

因为

self.r_m_s
是非Copy类型,它在引用(输入参数
&mut self
)后面时也不能移出,所以它的生命周期与
&mut self
相同。

这并不符合预期,因为我们对

&'a mut String
字段有明确的生命周期注释
r_m_s

让我们用下面的例子来验证一下:

#[derive(Debug)]
struct F<'a> {
    r: &'a mut Option<String>,
    r2: Option<&'a mut String>,
}

fn main() {
    let mut s = "option string".to_string();
    let mut o_str = Some(s);

    let mut s2 = "option string 2".to_string();
    let o_str2 = Some(&mut s2);

    // create a structure holder mutable reference to Option<String>
    let mut f = F {
        r: &mut o_str,
        r2: o_str2, // o_str2 moved
    };

    // create a mutable reference, same as `&mut self`
    let as_mut_ref_self = &mut f;

    // now v is &mut String
    let Some(v) = as_mut_ref_self.r else {
       return; 
    };
    
    // use v to change the string, Ok
    v.push_str(" changed by v");

    // create a imutable reference to structure f
    // to end the lifetime of as_mut_ref_self
    let as_ref_self = &f;
   
    // !!! let's check how about the lifetime of v ?
    // !!! uncomment below line will cause error,
    // !!! now we know:
    // !!! the lifetime of v is tied to the as_mut_ref_self
    // println!("{:?}", v);
    
    println!("{:?}", as_ref_self);
}

如果

self.r_m_s
&'a mut Option<String>
这样的复合类型呢,例如:

let Some(ref mut v) = self.r_m_s else {
   todo!()
};
v

现在

v
的类型是
&mut String
,但是它的生命周期和
self.r_m_s
一样,我们会遇到同样的错误,这和问题中的
fn next
的情况是一样的。

2.不安全修复

现在的问题是返回值的生命周期不等于或长于所需的生命周期

'a
。既然我们知道
&mut String
应该在生命周期内有效
'a
让我们尝试直接切断它与
&mut self
生命周期的联系

2.1 一次尝试

更改

test_return_ref_mut_field
方法如下:

fn test_return_ref_mut_field(&mut self) -> &'a mut String { 
    let t: &'static mut String = unsafe {&mut *(self.r_m_s as *mut String)};
    t
}

以上代码将

&mut
转换为
*mut
原始指针,然后将原始指针转换回来。

虽然看起来像是临时创建并返回的局部变量

t
,但是原始指针有一个
'static
的生命周期,显然比
'a
要长。

对于您的代码,您可以尝试这种

unsafe
方式在
&mut node.elem
中转换
Some(&mut node.elem)

**注意:此不安全的修复会存在

multiple mut reference
问题,请尝试以下示例:**

#[derive(Debug)]
struct Foo<'a> {
    r_m_s: &'a mut String, // non-Copy type
    r_i_s: &'a String, // Copy type
}

impl<'a> Foo<'a> {
    fn test_return_ref_field(&self) -> &'a String {
        self.r_i_s
    }
  
    fn test_return_ref_mut_field(&mut self) -> &'a mut String {
        let t:&'static mut String = unsafe {&mut *(self.r_m_s as *mut String)};
        t
    }
}

fn main() {
    let mut mut_string = "mut string".to_string();
    let string = "imutable string".to_string();

    let mut f = Foo {
        r_m_s: &mut mut_string,
        r_i_s: &string,
    };

    let ref_mut_1 = f.test_return_ref_mut_field();
    let ref_mut_2 = f.test_return_ref_mut_field();
    let ref_mut_3 = f.test_return_ref_mut_field();

    ref_mut_1.push_str(" by ref_mut_1");
    ref_mut_2.push_str(" by ref_mut_2");
    ref_mut_3.push_str(" by ref_mut_3");
    
    println!("{:#?}", f);

    println!("ref_mut_1: {}\nref_mut_2: {}\nref_mut_3: {}", 
        ref_mut_1, ref_mut_2, ref_mut_3);
    
    println!("{:#?}", f);
}

但是对于

IterMut
来说,由于每次调用
next
时,
self.this
都会更新为指向
next
节点,直到到达
None
。没有机会对同一值进行两次可变引用。

2.2 再次尝试

对于您的代码,您还可以将

IterMut
结构更改为:

pub struct IterMut<'a, T>{
    this: *mut Option<Box<Node<T>>>,
}

但是会导致错误

'a not used
,使用phantomdata来保存它:

use std::marker::PhantomData;
pub struct IterMut<'a, T>{
    this: *mut Option<Box<Node<T>>>,
    phantom: PhantomData<&'a ()>,
}

fn iter_mut
应更改为:

this: &mut self.head as *mut _,

并在

fn next
中使用它:

let t = unsafe{ &mut *self.p }

2.3 使用
Option<&'a mut T>
代替
&'a mut T

这个解决方案已经在其他答案中提到过。

Option
有一个方便的
take
方法,它是一个带有不安全代码的神奇功能实现,您可以将其视为一些在安全模式下无法完成的常见任务的官方解决方法。

看看下面的例子:

#[derive(Debug)]

struct F<'a> {
    r: Option<&'a mut String>,
}

fn main() {
    let mut s: String = "ssssss".to_string();
    let o_str: Option<&mut String> = Some(&mut s);

    // create a structure holder mutable reference to Option<String>
    let mut f: F<'_> = F {
        r: o_str,
    };

        
    // create a mutable reference
    let as_mut_ref_self = &mut f;
    
    // take the value of f.r by f,
    // and f.r will be replaced by None,
    let take = f.r.take();

    // v is &mut String
    let Some(v) = take else {
        return;
    };

    // to end the lifetime of as_mut_ref_self
    let as_ref_self = &f;

    // mutable reference droped already, 
    // now let's verify v is still valid.
    println!("{}", v);
}
© www.soinside.com 2019 - 2024. All rights reserved.