Доступ к неизменяемым методам во время итерации по изменяемому члену

Изучая итераторы в 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()
    }
}

Цель методов iter и 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));
        }
    }
}

Есть ли соглашение или передовая практика для изменения внутренних членов структуры, при этом разрешая доступ к неизменяемым членам и методам? Или это полностью нарушает гарантии безопасности?


person Michael Pierce    schedule 28.10.2014    source источник
comment
Вы действительно хотите сделать width и height общедоступными? Кто угодно мог изменить свою ценность, и все сломалось! Вместо этого они должны быть представлены с помощью методов получения.   -  person Francis Gagné    schedule 29.10.2014
comment
@ FrancisGagné: Ой! Это очень хороший момент. Я лениво предполагал, что все в Rust будет неизменным, но это явно не так.   -  person Michael Pierce    schedule 29.10.2014


Ответы (3)


У Матье М. правильная идея: использовать итератор для возврата row и 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, предоставленный модулем slice, а также width и height из таблицы. Реализовать трейт Iterator так же просто, как передать вызов next внутреннему итератору.

Структура 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, но у нас по-прежнему нет row и 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);
    }
}
person Michael Pierce    schedule 31.10.2014

Это не проблема изменчиво-неизменяемая, это просто двойное заимствование. Если бы внутренний вызов метода был &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);
    }
}

манеж

Помогло бы и выделение get_row_column отдельной функции.

person Manishearth    schedule 28.10.2014

Я бы сказал, что проблема здесь в дырявой абстракции: 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 ++.

person Matthieu M.    schedule 29.10.2014
comment
Мне это очень нравится, но мне не удалось его скомпилировать из-за ошибок, связанных с cannot infer an appropriate lifetime for lifetime parameter 'r in function call due to conflicting requirements. - person Michael Pierce; 29.10.2014