1

在学习 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()
    }
}

iterand方法的目标iter_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));
        }
    }
}

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

4

3 回答 3

2

Matthieu M. 有一个正确的想法:使用迭代器返回rowand column,而不是直接暴露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提供的迭代器,以及表中的and 。实现trait 就像将调用传递给内部迭代器一样简单。slicewidthheightIteratornext

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

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,但我们仍然没有rowand column。为此,我们可以添加我们自己的迭代器适配器,通过增加当前行和列的单独计数来模仿模块中的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()
    }
}

这个适配器是通用的,所以它可以用于TableItems或者MutTableItems(或我们将来选择提出的任何其他东西)。

最后一步是创建返回实例的方法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);
    }
}
于 2014-10-31T14:05:45.110 回答
1

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

虽然在这种特殊情况下不会发生这种情况,但对被迭代的片段有多个别名可能会导致迭代器失效。

在这种情况下,使用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);
    }
}

围栏

制作get_row_column一个单独的函数也会有所帮助。

于 2014-10-28T18:33:45.110 回答
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++ 模板别名功能很多,在这里。

于 2014-10-29T08:26:14.500 回答