Как удалить элемент из вектора в Rust, зная его значение?
Основная проблема: изменяемый доступ во время итерации
Вы правы — в Rust нельзя одновременно итерироваться по вектору и изменять его (удалять элементы), потому что это нарушает правила заимствования. Когда вы вызываете numbers.iter(), вы получаете неизменяемую ссылку на вектор, а numbers.remove(i) требует изменяемую ссылку. Компилятор блокирует такой код.
Решение: метод retain() для удаления всех совпадений
Самый простой и идиоматичный способ — использовать метод retain(). Он принимает замыкание (closure), которое возвращает bool: true — оставить элемент, false — удалить. Вот как это работает:
let mut numbers = vec![1, 2, 3, 4, 5];
numbers.retain(|&x| x != 3);
println!("{:?}", numbers); // [1, 2, 4, 5]
Если нужно удалить все вхождения значения:
let mut numbers = vec![1, 2, 3, 3, 4, 3];
numbers.retain(|&x| x != 3);
println!("{:?}", numbers); // [1, 2, 4]
Решение: удаление только первого вхождения
Если нужно удалить только первый элемент, равный заданному, используйте position() для поиска индекса, а затем remove():
let mut numbers = vec![1, 2, 3, 3, 4];
let target = 3;
if let Some(pos) = numbers.iter().position(|&x| x == target) {
numbers.remove(pos);
}
println!("{:?}", numbers); // [1, 2, 3, 4]
Метод position() возвращает Option<usize> — индекс первого совпадения или None, если элемент не найден. Мы используем if let, чтобы безопасно обработать оба случая.
Производительность и альтернативы
retain()— O(n), сдвигает элементы в памяти, но работает эффективно для массового удаления.position() + remove()— тоже O(n), ноremove()дополнительно сдвигает все следующие элементы влево, что может быть затратно для больших векторов.- Если порядок не важен, можно использовать
swap_remove()— он заменяет удаляемый элемент последним и удаляет последний, работая за O(1):
let mut numbers = vec![1, 2, 3, 4, 5];
let target = 3;
if let Some(pos) = numbers.iter().position(|&x| x == target) {
numbers.swap_remove(pos);
}
println!("{:?}", numbers); // [1, 2, 5, 4] — порядок изменён!
Важные замечания
- Всегда проверяйте, найден ли элемент, через
if letилиmatch, чтобы избежать паники при вызовеremove()с несуществующим индексом. - Для пользовательских типов данных замыкание должно сравнивать поля, например:
people.retain(|p| p.id != 42); - Если вектор очень большой и удаление происходит часто, рассмотрите использование
Vec::retain()илиdrain_filter()(нестабильный API, доступен в nightly).
Выберите подход в зависимости от задачи: retain() для удаления всех совпадений, position() + remove() для первого вхождения, swap_remove() когда порядок не важен.
Альтернативный подход: фильтрация через итераторы
Если вам не нужно изменять исходный вектор, а достаточно получить новый, можно использовать итераторы и метод filter():
let numbers = vec![1, 2, 3, 4, 5];
let filtered: Vec<i32> = numbers.into_iter().filter(|&x| x != 3).collect();
println!("{:?}", filtered); // [1, 2, 4, 5]
Этот подход потребляет исходный вектор (передаёт владение), создавая новый. Если нужно сохранить старый вектор, используйте iter() и клонирование:
let numbers = vec![1, 2, 3, 4, 5];
let filtered: Vec<i32> = numbers.iter().filter(|&&x| x != 3).cloned().collect();
// numbers всё ещё доступен
println!("{:?}", numbers); // [1, 2, 3, 4, 5]
Этот метод особенно полезен, когда вы работаете с неизменяемыми данными или хотите сохранить оригинал для других целей. Однако помните, что создание нового вектора требует дополнительной памяти.