Вектор Rust
Перевод | Автор оригинала: Nicolas Fränkel
Скажу честно: изначально я хотел описать все коллекции, доступные в Rust, а также связанные с ними концепции. Затем я начал немного копаться в этом и решил, что это будет (намного) слишком долго. По этой причине я ограничу область видимости типом Vec.
Это 9-й пост в серии статей о Start Rust.
- Моя первая чашка Rust
- Моя вторая чашка Rust
- Упражнения "Рустлингс" - часть 1.
- Упражнения "Рустлингс" - часть 2.
- Rust на интерфейсе.
- Контроллер Rust для Kubernetes
- Rust и JVM
- diceroller, образец проекта на Rust.
- Вектор Rust (этот пост)
Вот диаграмма, в которую мы углубимся:
Основы вектора
Из документации:
Тип непрерывного растущего массива, записываемый как Vec
и произносимый как «вектор».
Как и в случае с Java ArrayList, Vec поддерживается массивом. Когда массив достигает полной емкости, Vec резервирует новый массив с большей емкостью и копирует элементы из исходного массива во вновь созданный. Затем освобождает первое. Обратите внимание, что коллекция может выделять массив большего размера, чем необходимо, чтобы избежать частого перераспределения.
Чтобы добавить значения в Vec, нам нужно использовать дополнительный шаг, например, функцию push():
let mut v = Vec::new();
v.push(1);
v.push(5);
Функции, которые создают новые Vec, инициализируют их без элементов. Если мы хотим создать Vec и значения одновременно, Rust предлагает vec! макрос. Мы можем заменить приведенный выше код следующим однострочным:
let v = vec![1, 5];
На этом этапе, если вы уже немного поработали в Rust, вы, вероятно, не узнали ничего нового. Вместо того, чтобы описывать каждую функцию в Vec - они очень похожи на те, что есть в других языках - давайте немного исследуем их.
Вектор и итератор
Итератор - это шаблон проектирования, описанный в классической книге "Шаблоны проектирования: элементы объектно-ориентированного программного обеспечения многократного использования":
Намерение: предоставить объект, который пересекает некоторую совокупную структуру, абстрагируясь от предположений о реализации этой структуры.
- Шаблон итератора
Хотя в книге описан Iterator, ничто не ограничивает этот шаблон только языками ООП. Это довольно распространенная абстракция.
Rust предоставляет трейт Iterator, а Vec реализует его. Следовательно, мы можем просмотреть его значения:
let v = vec![1, 5];
let mut iter = v.iter();
loop {
let value = iter.next();
if value.is_some() {
println!("value: {}", value.unwrap());
} else {
break;
}
}
- Создайте итератор. Поскольку вызов next() изменяет состояние iter, он должен быть изменяемым.
- Получите следующее значение
Если бы я получал доллар за каждую ошибку, возникшую из-за неуместного оператора break, я бы сейчас наверняка был богат. Если вы сломаетесь слишком рано, вы потеряете ценности; слишком поздно, и ад вырвется наружу (каламбур). По этой причине, for наряду с итератором лучше подходит. Мы можем переписать приведенный выше код как:
let v = vec![1, 5];
for value in v.iter() {
println!("value: {}", value);
}
- Короче, безопаснее, а значит, лучше
Слишком много итераторов для выполнения итерации
Что интересно, мы можем немного изменить приведенный выше код, чтобы удалить вызов iter(). Это все еще работает!
let v = vec![1, 5];
for value in v {
println!("value: {}", value);
}
- Магия!
На самом деле это не волшебство, а синтаксический сахар Rust в действии. циклы for принимают итераторы. Тем не менее, некоторые экземпляры можно преобразовать в итераторы «на лету». Тип должен реализовывать трейт IntoIterator и его функцию into_iter(), чтобы иметь право. Как видно из диаграммы выше, это случай Vec.
Если вы спросите себя, в чем разница между iter() и into_inter(), утешитесь тем, что меня тоже интересовало. После некоторого исследования я нашел этот блестящий ответ:
TL;DR:
- Итератор, возвращаемый into_iter, может давать любой из T, &T или &mut T, в зависимости от контекста.
- Итератор, возвращаемый iter, по соглашению даст &T.
- Итератор, возвращаемый iter_mut, по соглашению даст &mut T.
- В чем разница между iter и into_iter?
Наконец, вы можете создать Vec из итератора благодаря FromIterator.
struct Foo {
count: u8
}
impl Iterator for Foo {
type Item = u8;
fn next(&mut self) -> Option<Self::Item> {
match self.count {
0 => {
self.count = self.count + 1;
Option::Some(1)
}
1 => {
self.count = self.count + 1;
Option::Some(5)
}
_ => None
}
}
}
let i = Foo { count: 0 };
let v = Vec::from_iter(i);
for value in v {
println!("value: {}", value);
}
- Реализуем собственный Итератор.
- Сначала верните 1, затем 5, затем ничего.
- Создайте Vec из нашего итератора.
Выделение памяти
До сих пор мы упустили из виду один аспект Vec: на самом деле это определение Vec<T, A: Allocator = Global>. Хотя T - это тип элементов, содержащихся в Vec, мы не рассматривали A, который выделяет память. Я не буду описывать эту тему подробно, потому что в настоящее время у меня нет соответствующих знаний. Тем не менее, я считаю, что упомянуть об этом интересно, поскольку ни один другой язык, о котором я знаю, не дает такой возможности.
Реализация Allocator может выделять, увеличивать, сжимать и освобождать произвольные блоки данных, описанные с помощью Layout.
- std::alloc::Allocator
Rust по умолчанию использует глобальный распределитель памяти. Он делегирует другому распределителю: это система, если вы не зарегистрируете его с атрибутом #[global_allocator].
Таким образом, Vec<T, A> позволяет использовать другой распределитель, нежели тот, который определен централизованно. Одним из возможных вариантов использования может быть создание распределителя, отслеживающего выделенные байты только для экземпляра Vec.
Вывод
На этом я закончил мои увлечения Vec. В этом посте я попытался избежать обычной обработки такого рода постов и немного исследовать их. Хотя в большинстве случаев выделенный распределитель памяти не требуется, я думаю, что это хороший трюк, о котором следует помнить на всякий случай. Чтобы пойти дальше:
- Модуль std::collections
- Сохранение списков значений с векторами
- Struct std::vec::Vec
- В чем разница между iter и into_iter?