Rust-实现一个产生可变引用的迭代器

3次阅读

共计 2948 个字符,预计需要花费 8 分钟才能阅读完成。

前言

笔者想实现一个 RowMutIter, 它产出 RatMat 某行的元素的可变引用,设想中可以这样使用它:


for x in ratmat.get_mut_row(1).unwrap(){*x += 1;    // x 类型是 &mut Node}

结果遇到大坑

报错代码

1. 无法推导生命周期


pub struct RowMutIter<'a>{
    pub ref_vec: &'a mut Vec<Node>,
    pub r_index: usize, 
    pub index: Cell<usize>,
}

impl<'a> Iterator for RowMutIter<'a>{
    type Item = &'a mut Node;

    fn next(&mut self) -> Option<Self::Item>{*self.index.get_mut() += 1;
        self.ref_vec.get_mut(self.index.get() - 1)  # ============ 这里!}
}

编译器报错:

error[E0495]: cannot infer an appropriate lifetime for lifetime parameter in function call due to conflicting requirements
   --> src\rational_matrix.rs:436:9
    |
436 |         self.ref_vec.get_mut(self.index.get() - 1)
    |         ^^^^^^^^^^^^
    |
note: first, the lifetime cannot outlive the anonymous lifetime #1 defined on the method body at 434:5...
   --> src\rational_matrix.rs:434:5
    |
434 | /     fn next(&mut self) -> Option<Self::Item>{435 | |         *self.index.get_mut() += 1;
436 | |         self.ref_vec.get_mut(self.index.get() - 1)
437 | |     }
    | |_____^
note: ...so that reference does not outlive borrowed content
   --> src\rational_matrix.rs:436:9
    |
436 |         self.ref_vec.get_mut(self.index.get() - 1)
    |         ^^^^^^^^^^^^
note: but, the lifetime must be valid for the lifetime 'a as defined on the impl at 431:6...
   --> src\rational_matrix.rs:431:6
    |
431 | impl<'a> Iterator for RowMutIter<'a>{
    |      ^^
    = note: ...so that the types are compatible:
            expected std::iter::Iterator
               found std::iter::Iterator

分析 1

生命周期无法推导。因为输入参数包含 self,则输出要和 self 是一致的。但是 Item=&’a Node,没办法推导 ’a 和 self 的关系。
那么修改为:

type Item = &Node; 

可以不?不可以,编译器提示 missing lifetime parameter
所以这里的设计有问题。

2. 未约束的生命周期

修改代码如下,这是笔者想表达的:

impl<'a,'b:'a> Iterator for RowMutIter<'b>{
    type Item = &'a mut Node;

    fn next(&mut self) -> Option<Self::Item>{*self.index.get_mut() += 1;
        self.ref_vec.get_mut(self.index.get() - 1)
    }
}

报错:


error[E0207]: the lifetime parameter `'a` is not constrained by the impl trait, self type, or predicates
   --> src\rational_matrix.rs:431:6
    |
431 | impl<'a,'b:'a> Iterator for RowMutIter<'b>{|      ^^ unconstrained lifetime parameter

分析 2

根据生命周期省略规则,如果传入参数有 &self or &mut self, 那么返回引用的 lifetime 与 self 一致。但是 Item = &’a Node, 无法得知 ‘a 与 self 的关系,所以报错。

解决办法

1. 修改设计

费了半天,终于找到一份对笔者有帮助的资料:
Can I write an Iterator that mutates itself and then yields a reference into itself?
一位答主指出,compiler 不知道 self 和返回值的 lifetime 的关系,可以添加:

    fn next(&'a mut self) -> Option<&'a [i8]>

但是这违反了 Iterator trait 的定义。后者要求:

    fn next(&mut self) -> Option<Self::Item>
            ↑------ 必须这样写

答主补充:

The Iterator trait is designed for return values that are independent of the iterator object, which is necessary for iterator adaptors like .collect to be correct and safe.

为什么笔者打算实现这个 trait? 因为笔者的 RatMat 是有理数矩阵,常常需要做一些行运算、列运算,比如行倍乘、行加法等,将来可能支持换行,删行等操作,如果这些能够配合 std 一系列 Iterator 工具,那就很有用了。可惜此路不通。
但是笔者后来想,这种对行列的操作也可以通过:

    fn apply_to_row<F:FnOnce()..)(row_index:usize, f: F) 
    fn apply_to_col<F:FnOnce()..)(col_index:usize, f: F) 

这样的方式实现。

2.Unsafe?

笔者查看 Vec 的 IterMut 的实现,其内部使用了大量 unsafe。此外还有一篇博文:
Iterators yielding mutable references
提到了不用 unsafe 无法实现这样的迭代器,笔者打算细细研读。

疑问

笔者还编写了 RowIter, 也就是不可变的行迭代器。其代码基本与 RowMutIter 相同,可是前者顺利通过编译,而后者不行。这又是为什么?与不可变引用本身实现了 Copy 有关吗?如果是,Copy 在这起到什么作用?

最终解决方法

(未完)

参考

一些 (可能) 有用的参考:
1.Rust 返回引用的不同方式 (译文)
2.Strategies for Returning References in Rust (原文)
3.Rust 学习笔记 3 所有权和借用
4.RustPrimer 引用借用
5. 如何实现一个可变迭代器
6.Vec< Vec<(k, v)>> 的可变迭代器,这一篇用 Flat_map() 把二维的 for{for{..}}结构展开为一维结构。
7. 知乎 -rust 中如何实现返回可变引用的迭代器?

正文完
 0