当前位置: 首页 > 知识库问答 >
问题:

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

杭英杰
2023-03-14

在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     }
               ^

什么是实现我在这里尝试的正确方法?我可以添加一个<code>set</code>方法,该方法接受行和列编号,并显式循环行和列索引,但用户必须担心行主排序与列主排序:

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个答案

呼延运恒
2023-03-14
匿名用户

我认为这里的问题是一个抽象漏洞:< code>index不应该一开始就暴露给用户。

因此,您需要更改接口,以便在迭代时直接提供< code>(row,column)而不是< code>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模板别名功能很多,这里。

杨和蔼
2023-03-14

这不是可变不可变的问题,这只是一个双重借用。如果内部方法调用是

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

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

婴儿用Geofence

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

袁琪
2023-03-14

Matthieu M .的想法是对的:使用迭代器返回< code >行和< code >列,而不是直接公开< code >索引。不幸的是,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()
    }
}

此结构包含切片模块提供的迭代器,以及表中的widthheight。实现<code>迭代器

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
        }
    }
}

现在,这些迭代器本身并没有真正给我们带来什么;它们返回< code >表的值,但是我们仍然没有< code >行和< code >列。为此,我们可以添加自己的迭代器适配器,通过递增当前行和列的单独计数来模拟< code>iter模块中的< code>Enumerate特征:

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(或我们选择在未来提出的任何其他内容)。

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

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命名为enumerate,但编译器似乎在此之前找到了enumerateIterator实现。

有了这个完整的解决方案,可以像这样访问表:

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);
    }
}
 类似资料:
  • 问题内容: Go 1.7 beta 1今天早上发布,这是Go 1.7的发行说明草案 。新功能已添加到程序包中。的文档给出了一个示例: 的文档还给出了以下解释: 例如,如果p指向包含文件描述符d的结构,并且p具有关闭该文件描述符的终结器,并且函数中对p的最后使用是对syscall的调用。Write(pd,buf,size ),则一旦程序进入syscall.Write,p可能无法访问。终结器可能会在此

  • 继承一个迭代器()方法以生成一个。 但是我需要一个而不是。 例如,给定以下字符串: ...我需要将字符串的这些部分作为传递给特定的库。调用会产生一个。因此,如果我可以将该转换为其元素的,我会设置。

  • 有没有办法在for循环中使变量可迭代?我正在尝试制作一个密码生成器,对于for循环,我使用一个输入数字的变量。 当我试图在循环中使用变量时,我得到< code > ' int ' object is not iterable 。

  • 这个问题与前一个问题相关,在前一个问题中,我们注意到init捕获lambdas与Boost的范围和迭代器不兼容,因为一些相当模糊且嵌套很深的故障可能很难通过破解Boost来解决。射程源。 接受的答案建议将lambda存储在对象中。为了避免潜在的函数调用开销,我编写了两个函数对象,可以作为潜在的解决方法。它们在下面的代码中被称为和 不使用和编译行,并正确打印行的的实例。 然而,标准草案提到 5.1.

  • 我正在将我的google dataflow java 1.9迁移到beam 2.0,并尝试使用BigtableIO。写 在大舞台前的巴黎公园里,我正在努力让它变得更容易接受。 上述代码引发以下异常InvalidProtocolBufferException:协议消息结束组标记与预期标记不匹配 v是对象列表(Vitals.class)。hbase api使用Put方法创建变异。如何创建将与Bigta

  • 下面是一个不可变类的示例: 下面是类的实现: 当我创建的实例时,我正在的构造函数中对进行深度复制,但是我能够通过更新的值,这种方法破坏了my类的不变性。我在这里做错了什么?