Как получить несколько изменяемых ссылок на элементы в Vec?

У меня большая вложенная структура данных, и я хотел бы выделить несколько частей для передачи для обработки. В конечном итоге я хочу отправить разделы в несколько потоков для обновления, но я хотел бы немного помочить ноги, разбираясь в простом примере, который я проиллюстрировал ниже. В C я бы просто собрал массив соответствующих указателей. Это кажется выполнимым в Rust, поскольку внутренние векторы никогда не будут нуждаться в нескольких изменяемых ссылках. Вот пример кода.

fn main() {
    let mut data = Data::new(vec![2, 3, 4]);
    // this works
    let slice = data.get_mut_slice(1);
    slice[2] = 5.0;
    println!("{:?}", data);

    // what I would like to do
    // let slices = data.get_mut_slices(vec![0, 1]);
    // slices[0][0] = 2.0;
    // slices[1][0] = 3.0;
    // println!("{:?}", data);
}

#[derive(Debug)]
struct Data {
    data: Vec<Vec<f64>>,
}

impl Data {
    fn new(lengths: Vec<usize>) -> Data {
        Data {
            data: lengths.iter().map(|n| vec![0_f64; *n]).collect(),
        }
    }

    fn get_mut_slice(&mut self, index: usize) -> &mut [f64] {
        &mut self.data[index][..]
    }

    // doesnt work
    // fn get_mut_slices(&mut self, indexes: Vec<usize>) -> Vec<&mut [f64]> {
    //     indexes.iter().map(|i| self.get_mut_slice(*i)).collect()
    // }
}

person cleverpiggy    schedule 14.02.2021    source источник
comment
Если бы это было скомпилировано, для data.get_mut_slices(vec![0, 0]) было бы возможно неопределенное поведение, возвращающее две изменяемые ссылки на одни и те же элементы.   -  person kmdreko    schedule 14.02.2021


Ответы (2)


Это возможно с использованием безопасного Rust, если вы очень осторожны. Уловка состоит в том, чтобы использовать небезопасный код Rust в стандартной библиотеке за безопасными .iter_mut() и .nth() методами на Vec. Вот рабочий пример с комментариями, объясняющими код в контексте:

fn main() {
    let mut data = Data::new(vec![2, 3, 4]);

    // this works
    let slice = data.get_mut_slice(1);
    slice[2] = 5.0;
    println!("{:?}", data);

    // and now this works too!
    let mut slices = data.get_mut_slices(vec![0, 1]);
    slices[0][0] = 2.0;
    slices[1][0] = 3.0;
    println!("{:?}", data);
}

#[derive(Debug)]
struct Data {
    data: Vec<Vec<f64>>,
}

impl Data {
    fn new(lengths: Vec<usize>) -> Data {
        Data {
            data: lengths.iter().map(|n| vec![0_f64; *n]).collect(),
        }
    }

    fn get_mut_slice(&mut self, index: usize) -> &mut [f64] {
        &mut self.data[index][..]
    }

    // now works!
    fn get_mut_slices(&mut self, mut indexes: Vec<usize>) -> Vec<&mut [f64]> {
        // sort indexes for easier processing
        indexes.sort();
        let index_len = indexes.len();

        // early return for edge case
        if index_len == 0 {
            return Vec::new();
        }

        // check that the largest index is in bounds
        let max_index = indexes[index_len - 1];
        if max_index > self.data.len() {
            panic!("{} index is out of bounds of data", max_index);
        }

        // check that we have no overlapping indexes
        indexes.dedup();
        let uniq_index_len = indexes.len();
        if index_len != uniq_index_len {
            panic!("cannot return aliased mut refs to overlapping indexes");
        }

        // leverage the unsafe code that's written in the standard library
        // to safely get multiple unique disjoint mutable references
        // out of the Vec
        let mut mut_slices_iter = self.data.iter_mut();
        let mut mut_slices = Vec::with_capacity(index_len);
        let mut last_index = 0;
        for curr_index in indexes {
            mut_slices.push(
                mut_slices_iter
                    .nth(curr_index - last_index)
                    .unwrap()
                    .as_mut_slice(),
            );
            last_index = curr_index;
        }

        // return results
        mut_slices
    }
}

детская площадка


Я считаю, что я узнал, что компилятор Rust требует итератора в этой ситуации, потому что это единственный способ узнать, что каждый фрагмент mut происходит из другого вектора.

На самом деле компилятор этого не знает. Все, что ему известно, это то, что итератор возвращает ссылки на mut. Базовая реализация использует небезопасный Rust, но сам метод iter_mut() безопасен, потому что реализация гарантирует, что каждый mut ref будет генерироваться только один раз и что все mut ref уникальны.

Будет ли компилятор жаловаться, если в цикле for будет создан другой mut_slices_iter (который может получить одни и те же данные дважды)?

да. Вызов iter_mut() на Vec взаимно заимствует его, а перекрывающиеся изменяемые заимствования одних и тех же данных противоречат правилам владения Rust, поэтому вы не можете вызвать iter_mut() дважды в одной и той же области (если итератор, возвращенный первым вызовом, не будет отброшен перед вторым вызовом).

Также я прав в том, что метод .nth будет вызывать next() n раз, так что в конечном итоге это theta (n) на первой оси?

Не совсем. Это реализация по умолчанию для nth, НО итератор, возвращаемый вызовом iter_mut() на Vec, использует собственная индивидуальная реализация, и кажется, что он пропускает предыдущие элементы в итераторе без вызова next(), поэтому он должен быть таким же быстрым, как если бы вы просто регулярно индексировали в Vec, т.е. получение 3 случайно проиндексированных элементов с использованием .nth() будет таким же быстрым на итераторе из 10000 элементов, как и на итераторе из 10 элементов, хотя это особенно верно только для итераторов, созданных из коллекций, которые поддерживают быстрый произвольный доступ, например Vecs.

person pretzelhammer    schedule 14.02.2021
comment
Я считаю, что из первых двух ответов я понял, что компилятор Rust требует итератора в этой ситуации, потому что это единственный способ узнать, что каждый фрагмент mut исходит из другого вектора. Хотя в этом примере менее ясно, чем в следующем. Будет ли компилятор жаловаться, если в цикле for был создан другой mut_slices_iter (который мог бы получить одни и те же данные дважды)? Также я прав, что метод .nth будет вызывать next () n раз, поэтому в конечном итоге это theta (n) на первой оси? Он должен быть достаточно производительным, но я хотел бы знать. - person cleverpiggy; 15.02.2021
comment
@cleverpiggy Я обновил свой ответ, чтобы ответить на последующие вопросы в вашем комментарии. Если мой ответ помог вам, пожалуйста, подумайте о том, чтобы проголосовать за него (нажав серую стрелку над оценкой ответа) и принять его (нажав серую галочку под оценкой ответа), спасибо! - person pretzelhammer; 15.02.2021

Если вам нужны уникальные индексы, что на самом деле имеет больше смысла, поскольку вы не можете / не иметь двух изменяемых ссылок на один и тот же элемент. Вы можете использовать HashSet вместо Vec и использовать несколько комбинаций итераторов:

    fn get_mut_slices(&mut self, indexes: HashSet<usize>) -> Vec<&mut [f64]> {
        self.data
            .iter_mut()
            .enumerate()
            .filter(|(i, _)| indexes.contains(i))
            .map(|(_, e)| e.as_mut_slice())
            .collect()
    }

Детская площадка

Вы по-прежнему можете использовать Vec для этой опции, но при использовании contains это будет намного неэффективнее:

    fn get_mut_slices(&mut self, indexes: Vec<usize>) -> Vec<&mut [f64]> {
        self.data
            .iter_mut()
            .enumerate()
            .filter(|(i, _)| indexes.contains(i))
            .map(|(_, e)| e.as_mut_slice())
            .collect()
    }
person Netwave    schedule 14.02.2021