6

Делаю задачу для тренировки, в последнем for выскакивает ошибка о выходе за границы вектора :

thread '<main>' panicked at 'index out of bounds: the len is 22 but the index is 22', ../src/libcollections/vec.rs:1106*

Не могу понять, почему так происходит, в цикле for не проверяется результат len() при каждом новом проходе цикла?

fn main(){
    let e = 0.005;
    let n = 30;
    let mut vector: Vec<f32>;
    vector = Vec::new();
    for i in 0..n {
        let result:f32 = 1./(i as f32 + 1.).powf(2.0);
        vector.push( result );
    }
    for i in 0..vector.len(){
        println!("len:{:?}", vector.len());
        if vector[i] < e {
            println!("{:?}", vector[i]);
            vector.remove(i);
        }
    }
}

Я переделал под while и всё работает, но заводить счётчик до цикла как-то не красиво, да и i+=1; тоже, можно ли как-то это оформить посимпотичнее? Вот что получилось вместо последнего for

let mut i=0;
while( i < vector.len() ){
    if vector[i] < e {
        println!("{:?}", vector[i]);
        vector.remove(i);
    }
    i+=1;
}
Kyubey
  • 32,103
dzrkot
  • 405

2 Answers2

5

С помощью выражения 0..vector.len() вы получаете итератор, который возвращает числа от 0 до исходной длины вектора. Однако внутри цикла вы удаляете элементы, поэтому индекс выходит за границы.

Это не сишный for, здесь итератор создаётся один раз в самом начале, и его верхяя граница не обновляется вместе с изменением размера вектора. Если вы хотите менять длину вектора во время итерирования по нему, вам нужно использовать while, потому что в этом случае верхяя граница будет проверяться каждый раз.

К слову, при таком способе удаления инкрементировать переменную цикла следует, только если элемент не был удалён, иначе вы пропустите идущие подряд элементы, которые подлежат удалению.

Kyubey
  • 32,103
4

В добавление советую внимательно прочитать документацию по Vec и Iterator. Практически всегда, когда хочется сделать for i in 0..vec.len() в расте нужно использовать итерирование по вектору напрямую или какой-то метод самого вектора или итератора от него. В частности в данном случае гораздо лучше использовать Vec::retain(), а для построения Iterator::collect() и Iterator::map():

let mut vector = (0..n).map(|i| 1./(i as f32 + 1.).powf(2.0)).collect::<Vec<_>>();
vector.retain(|item| item >= e);

Плюс ещё в том, что если использовать библиотечные методы и итераторы, то не будет сгенерирован код для проверки выхода индекса за границы массива (bounds check), как при использовании операции индексирования [].

kstep
  • 306
  • не хотелось бы создавать новую тему, но как задать шаг в такой конструкции (0..n) ? Я в доке не нашёл... – dzrkot Dec 24 '15 at 07:28
  • 1
    Есть Range::step_by(), но он пока не стабилен, так что доступен только в nightly. Если хотите использовать stable, то можно нагородить конструкцию из filter: (0..n).filter(|&i| i % step == 0), или, в случае вектора: vec.into_iter().enumerate().filter(|&(i, _)| i % step == 0). В последнем случае с enumerate будет итератор по кортежам из индекса и элемента. – kstep Dec 24 '15 at 07:38
  • 1
    Ещё момент, into_iter() поглощает вектор и позволяет итераторировать по значению. Если вектор ещё будет нужен, то надо использовать iter() или iter_mut(), которые только заимствуют вектор, но итераторы будут по ссылке на элементы. – kstep Dec 24 '15 at 07:42
  • спасибо... да уж простой и лаконичный синтаксис(с).... – dzrkot Dec 24 '15 at 07:46
  • 1
    Дело привычки. Ну и потом, здесь это выглядит не очень хорошо в одну строчку. Если хорошо отформатировать, то выглядит даже симпатично. – kstep Dec 24 '15 at 07:49