迭代可变成员时访问不可变方法

时间:2014-10-28 17:18:43

标签: rust borrow-checker

在学习Rust中的迭代器时,我创建了以下结构来隐藏二维集合的实现:

use std::slice::{Items, MutItems};
use std::vec::{Vec};

pub struct Table<T> {
    pub width: uint,
    pub height: uint,
    data: Vec<T>
}

impl<T: Clone> Table<T> {
    pub fn from_elem(width: uint, height: uint, value: T) -> Table<T> {
        Table {
            width: width,
            height: height,
            data: Vec::from_elem(width * height, value)
        }
    }
}

impl<T> Table<T> {
    pub fn get_row_column(&self, index: uint) -> (uint, uint) {
        (index / self.width, index % self.width)
    }

    pub fn iter<'a>(&'a self) -> Items<'a, T> {
        self.data.iter()
    }

    pub fn iter_mut<'a>(&'a mut self) -> MutItems<'a, T> {
        self.data.iter_mut()
    }
}

iteriter_mut方法的目标是该结构的用户不必担心数据是以行主格式还是列主格式存储;迭代器只是以最有效的顺序提供元素。

但是,在使用这种数据结构时,我经常需要知道特定的行和列才能获得一些外部数据:

fn get_input(row: uint, column: uint) -> uint {
    row * 10 + column / 2
}

fn main() {
    let mut table = Table::from_elem(640, 480, 0u);

    for (index, value) in table.iter_mut().enumerate() {
        let (row, column) = table.get_row_column(index);
        *value = get_input(row, column);
    }
}

但是当我尝试调用get_row_column方法时,我得到以下编译器错误:

main.rs:56:33: 56:38 error: cannot borrow `table` as immutable because it is also borrowed as mutable
main.rs:56             let (row, column) = table.get_row_column(index);
                                           ^~~~~
main.rs:55:31: 55:36 note: previous borrow of `table` occurs here; the mutable borrow prevents subsequent moves, borrows, or modification of `table` until the borrow ends
main.rs:55         for (index, value) in table.iter_mut().enumerate() {
                                         ^~~~~
main.rs:59:6: 59:6 note: previous borrow ends here
main.rs:55         for (index, value) in table.iter_mut().enumerate() {
main.rs:56             let (row, column) = table.get_row_column(index);
main.rs:57             *value = get_input(row, column);
main.rs:58         }
main.rs:59     }
               ^

完成我在这里尝试做什么的正确方法是什么?我可以添加一个set方法,该方法获取行号和列号并显式循环遍历行和列索引,但是用户必须担心行主要与列主要排序:

impl<T> Table<T> {        
    fn get_index(&self, row: uint, column: uint) -> uint {
        row * self.width + column
    }

    pub fn set(&mut self, row: uint, column: uint, value: T) {
        let index = self.get_index(row, column);
        self.data[index] = value;
    }
}

fn main() {
    let mut table = Table::from_elem(640, 480, 0u);

    for row in range(0, table.height) {
        for column in range(0, table.width) {
            table.set(row, column, get_input(row, column));
        }
    }
}

是否有一个约定或最佳实践来改变结构的内部成员,同时仍允许访问不可变成员和方法?或者这完全违反了安全保障?

3 个答案:

答案 0 :(得分:2)

Matthieu M.有正确的想法:使用迭代器返回rowcolumn,而不是直接公开index。不幸的是,看起来Rust中的闭包当前是堆栈分配的,并且无法扩展到创建它们的堆栈帧之外,所以他提出的解决方案不会编译。

虽然使用迭代器适配器非常方便和简洁,但我们仍然可以通过创建新的迭代器对象来完成它。

关键是创建一个迭代器来跟踪我们需要的上下文,在这种情况下是表的维度:

pub struct TableItems<'a, T: 'a> {
    iter: Items<'a, T>,
    width: uint,
    height: uint
}

impl<'a, T> Iterator<&'a T> for TableItems<'a, T> {
    #[inline]
    fn next(&mut self) -> Option<&'a T> {
        self.iter.next()
    }
}

此结构包含Items模块提供的slice迭代器,以及表中的widthheight。实现Iterator特征就像将next调用传递给内部迭代器一样简单。

TableItems struct只返回不可变引用,但是我们可以为可变引用创建一个类似的引用:

pub struct MutTableItems<'a, T: 'a> {
    iter: MutItems<'a, T>,
    width: uint,
    height: uint
}

impl<'a, T> Iterator<&'a mut T> for MutTableItems<'a, T> {
    #[inline]
    fn next(&mut self) -> Option<&'a mut T> {
        self.iter.next()
    }
}

然后我们只需要添加一种方法将维度从Table对象传递给迭代器:

impl<T> Table<T> {
    pub fn iter<'a>(&'a self) -> TableItems<'a, T> {
        TableItems {
            iter: self.data.iter(),
            width: self.width,
            height: self.height
        }
    }

    pub fn iter_mut<'a>(&'a mut self) -> MutTableItems<'a, T> {
        MutTableItems {
            iter: self.data.iter_mut(),
            width: self.width,
            height: self.height
        }
    }
}

现在,这些迭代器本身并没有真正获得任何东西;他们会返回Table的值,但我们仍然没有rowcolumn。为了实现这一点,我们可以通过增加当前行和列的单独计数来添加我们自己的迭代器适配器,该适配器模拟Enumerate模块中的iter特征:

impl<'a, A, T: Iterator<A>> Iterator<((uint, uint), A)> for TableEnumerate<T> {
    fn next(&mut self) -> Option<((uint, uint), A)> {
        match self.iter.next() {
            Some(value) => {
                let ret = Some(((self.row_count, self.column_count), value));

                self.column_count += 1;
                if self.column_count == self.width {
                    self.row_count += 1;
                    self.column_count = 0;
                }
                ret
            },
            None => None
        }
    }

    #[inline]
    fn size_hint(&self) -> (uint, Option<uint>) {
        self.iter.size_hint()
    }
}

此适配器是通用的,因此可用于TableItemsMutTableItems(或我们将来选择提供的任何其他内容)。

最后一步是创建返回TableEnumerate实例的方法:

impl<'a, T> TableItems<'a, T> {
    pub fn enumerate_2d(self) -> TableEnumerate<TableItems<'a, T>> {
        let width = self.width;
        let height = self.height;

        TableEnumerate {
            iter: self,
            width: width,
            height: height,
            row_count: 0,
            column_count: 0
        }
    }
}

impl<'a, T> MutTableItems<'a, T> {
    pub fn enumerate_2d(self) -> TableEnumerate<MutTableItems<'a, T>> {
        let width = self.width;
        let height = self.height;

        TableEnumerate {
            iter: self,
            width: width,
            height: height,
            row_count: 0,
            column_count: 0
        }
    }
}

我本来希望将这些命名为enumerate,但看起来编译器会在此之前找到Iterator enumerate的实现。

使用这个完整的解决方案,表格可以像这样获得:

fn get_input(row: uint, column: uint) -> uint {
    row * 10 + column / 2
}

fn main() {
    let mut table = Table::from_elem(640, 480, 0u);

    for ((row, column), value) in table.iter_mut().enumerate_2d() {
        *value = get_input(row, column);
    }
}

答案 1 :(得分:1)

这不是一个可变的不可改变的问题,它只是一个双借。如果内部方法调用是&mut self方法,您将遇到相同的问题。您没有失去对不可变方法的访问权限,只要value在范围内,您就无法访问所有方法,因为value是对表的借用。

虽然在这种特殊情况下不会发生这种情况,但是对于正在迭代的部分有多个别名会导致迭代器失效。

在这种情况下,请使用map进行计算:

fn main() {
    let mut table = Table::from_elem(640, 480, 0u);
    let width = table.width;

    for (value, row, column) in table.iter_mut().enumerate().map(|(i,v)| (v, i / width, i % width) ) {
        *value = get_input(row, column);
    }
}

playpen

使get_row_column成为一个单独的函数也会有所帮助。

答案 2 :(得分:1)

我认为这里的问题是漏洞抽象之一:index永远不应该向用户公开。

因此,在迭代时,您需要将界面更改为直接提供(row, column)而不是index,然后使用会很简单。

类似的东西:

use std::iter::{Enumerate, Map}

impl<T> Table<T> {
    // Additions
    pub fn iter_enum<'a>(&'a self) -> Map<'a, (uint, &'a T), ((uint, uint), &'a T), Enumerate<Items<'a, T>>> {
        self.iter().enumerate().map(|(i, v)| ((i / self.width, i % self.width), v)
    }

    pub fn iter_mut_enum<'a>(&'a mut self) -> Map<'a, (uint, &'a mut T), ((uint, uint), &'a mut T), Enumerate<MutItems<'a, T>>> {
        self.iter_mut().enumerate().map(|(i, v)| ((i / self.width, i % self.width), v)
    }
}

注意:我希望C ++模板别名功能很多,在这里。