Вектор Rust

Перевод | Автор оригинала: Nicolas Fränkel

Скажу честно: изначально я хотел описать все коллекции, доступные в Rust, а также связанные с ними концепции. Затем я начал немного копаться в этом и решил, что это будет (намного) слишком долго. По этой причине я ограничу область видимости типом Vec.

Это 9-й пост в серии статей о Start Rust.

  1. Моя первая чашка Rust
  2. Моя вторая чашка Rust
  3. Упражнения "Рустлингс" - часть 1.
  4. Упражнения "Рустлингс" - часть 2.
  5. Rust на интерфейсе.
  6. Контроллер Rust для Kubernetes
  7. Rust и JVM
  8. diceroller, образец проекта на Rust.
  9. Вектор 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;
    }
}
  1. Создайте итератор. Поскольку вызов next() изменяет состояние iter, он должен быть изменяемым.
  2. Получите следующее значение

Если бы я получал доллар за каждую ошибку, возникшую из-за неуместного оператора break, я бы сейчас наверняка был богат. Если вы сломаетесь слишком рано, вы потеряете ценности; слишком поздно, и ад вырвется наружу (каламбур). По этой причине, for наряду с итератором лучше подходит. Мы можем переписать приведенный выше код как:

let v = vec![1, 5];
for value in v.iter() {                        
    println!("value: {}", value);
}
  1. Короче, безопаснее, а значит, лучше

Слишком много итераторов для выполнения итерации

Что интересно, мы можем немного изменить приведенный выше код, чтобы удалить вызов iter(). Это все еще работает!

let v = vec![1, 5];
for value in v {                               
    println!("value: {}", value);
}
  1. Магия!

На самом деле это не волшебство, а синтаксический сахар Rust в действии. циклы for принимают итераторы. Тем не менее, некоторые экземпляры можно преобразовать в итераторы «на лету». Тип должен реализовывать трейт IntoIterator и его функцию into_iter(), чтобы иметь право. Как видно из диаграммы выше, это случай Vec.

Если вы спросите себя, в чем разница между iter() и into_inter(), утешитесь тем, что меня тоже интересовало. После некоторого исследования я нашел этот блестящий ответ:

 TL;DR:

 - Итератор, возвращаемый into_iter, может давать любой из T, &T или &mut T, в зависимости от контекста.
 - Итератор, возвращаемый iter, по соглашению даст &T.
 - Итератор, возвращаемый iter_mut, по соглашению даст &mut T.

Наконец, вы можете создать 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. Реализуем собственный Итератор.
  2. Сначала верните 1, затем 5, затем ничего.
  3. Создайте Vec из нашего итератора.

Выделение памяти

До сих пор мы упустили из виду один аспект Vec: на самом деле это определение Vec<T, A: Allocator = Global>. Хотя T - это тип элементов, содержащихся в Vec, мы не рассматривали A, который выделяет память. Я не буду описывать эту тему подробно, потому что в настоящее время у меня нет соответствующих знаний. Тем не менее, я считаю, что упомянуть об этом интересно, поскольку ни один другой язык, о котором я знаю, не дает такой возможности.

Реализация Allocator может выделять, увеличивать, сжимать и освобождать произвольные блоки данных, описанные с помощью Layout.

диаграмма классов Rust's Allocator

Rust по умолчанию использует глобальный распределитель памяти. Он делегирует другому распределителю: это система, если вы не зарегистрируете его с атрибутом #[global_allocator].

Таким образом, Vec<T, A> позволяет использовать другой распределитель, нежели тот, который определен централизованно. Одним из возможных вариантов использования может быть создание распределителя, отслеживающего выделенные байты только для экземпляра Vec.

Вывод

На этом я закончил мои увлечения Vec. В этом посте я попытался избежать обычной обработки такого рода постов и немного исследовать их. Хотя в большинстве случаев выделенный распределитель памяти не требуется, я думаю, что это хороший трюк, о котором следует помнить на всякий случай. Чтобы пойти дальше: