Коллекции

Вектор

Последнее обновление: 02.06.2024

Для хранения набора однотипных данных в языке Rust предназначены массивы. Однако они имеют недостаток - после их определения мы не можем изменить их длину, не можем ни добавить новый элемент, ни удалить уже существующий. Этот недостаток призван устранить вектор, который представлен типом Vec<T> (а точнее структурой std::vec::Vec<T>). Вектор как раз и представляет динамически изменяемый набор однотипных данных.

Определение вектора

Есть разные способы определения вектора. Первый способ - вызов функции Vec::new(), которая создает пустой вектор:

let v: Vec<тип_данных> = Vec::new();

Например:

let v: Vec<i32> = Vec::new();

В данной случае также указан тип вектора - Vec<i32> - то есть вектор, который хранит значения типа i32.

Второй способ представляет применение специального макроса vec!, после которого в квадратных скобках через запятую перечисляются элементы вектора:

let v = vec! [значение1, значени2, ... значениеN];

Например:

let v = vec![1, 2, 3];

Здесь для вектора определены три элемента типа i32, поэтому вектор неявно будет иметь тип Vec<i32>

При этом вектор может быть и пустым:

let v: Vec<i32>= vec![];

Третий способ представляет заполнение вектора значениями по умолчанию:

let v = vec![val; repeat];

В квадратных скобках после макроса vec! указываются два значения. Первое значение указывает на значение по умолчанию, которым заполняется вектор, а второе значение - начальное количество элементов. Например:

let v = vec![5; 3];

Этот вектор будет аналогичен следущему:

let v = vec![5, 5, 5];

Добавление элементов

Для добавления элементов в вектор применяется метод push():

let mut users = Vec::new();
users.push("Tom");
users.push("Sam");
users.push("Bob");

Причем если предполагается изменение вектора, то его переменная должна быть определена с помощью оператора mut.

При это в примере выше указан тип вектора, так как Rust на основе добавляемых значений может определить тип. Однако все добавляемые значения, должны иметь один и тот же тип данных.

Для вывода всего вектора на консоль можно использовать тот же способ, что и при выводе массива:

fn main(){
   
    let users = vec!["Tom", "Sam", "Bob"];

    println!("{:?}", users);    // ["Tom", "Sam", "Bob"]
}

Обращение к элементам вектора

Обращение к элементам вектора производится так же, как и к элементам массива с помощью индексов:

вектор[индекс_элемента]

Например, получим и изменим некоторые элементы вектора:

fn main(){
  
    let mut users = vec!["Tom", "Sam", "Bob"];
	
	println!("1. {}", users[0]);	// Tom
	println!("2. {}", users[1]);	// Sam
	println!("3. {}", users[2]);	// Bob
	
	// меняем значение элемента
	users[1] = "Alice";
	println!("После изменения: 2. {}", users[1]);	// Alice
}

Консольный вывод:

1. Tom
2. Sam
3. Bob
После изменения: 2. Alice

Однако стоит отметить, что если мы попытаемся обратиться по несуществующему индексу, то мы получим ошибку. И в этом случае мы можем использовать более "мягкий" способ получения - через метод get(), в который передается индекс элемента:

fn main(){
   
    let users = vec!["Tom", "Sam", "Bob"];
 
    let second = users.get(1); // получаем элемент по индексу 1

    match second {

        Some(user) => println!("The second user is {}", user),
        None => println!("User not found")
    }
}
// Консольный вывод:
// The second user is Sam

Метод get() возвращает объект перечисления Option<&T> - если элемент действительно существует, то это будет значение Some(), в которое обернуто полученное значение. Если эе элемента по указанному индексу не существует, то это будет значение None. Соответственно с помощью конструкции match мы можем проверить полученное значение.

Перебор вектора

Для перебора вектора можно использовать цикл for:

fn main(){
  
    let users = vec!["Tom", "Sam", "Bob"];

	for user in &users{
		println!("{}", user);
	}
}

Обратите внимания, что циклу for передается не сам объект вектора users, а ссылка на него. Дело в том, что передача объекта в цикл меняет владение вектора. После чего мы не сможем ссылаться на этот вектор через переменную users. Проблему можно увидеть на следующем примере:

fn main(){
  
    let mut users = vec!["Tom", "Sam", "Bob"];

	for user in users{
		print!("{} ", user);
	}
	users.push("Alice");	// ! Ошибка - переменнаяя users более недействительна
}

Здесь в цикл передается не ссылка, а полноценный объект вектора. В итоге владение вектором перемещается от переменной users в цикл for. Поэтому на строке

users.push("Alice");

при компиляции мы получим ошибку, так как переменная users больше не представляет объект вектора.

Тип элементов вектора

Rust необходимо знать, какие типы будут в векторе во время компиляции, чтобы точно знать, сколько памяти в куче потребуется для хранения каждого элемента. Поэтому мы должны четко указать, какой тип будут иметь элементы вектора или создать вектор с элементами одного типа. Если бы Rust позволил вектору содержать любой тип, существовала бы вероятность того, что один или несколько типов вызовут ошибки при операциях, выполняемых с элементами вектора. Тем не менее мы можем в некоторой мере обойти это ограничение с помощью перечислений. Например:

enum Phones {
    Number(u64),
    Text(String),
}

fn main(){
    
    let phones = vec![ 
        Phones::Number(19876542310), 
        Phones::Text(String::from("1-987-654-3210"))
    ];

    for phone in &phones {
        match phone {
            Phones::Number(n) => println!("{n}"),
            Phones::Text(s) => println!("{s}"),
        }
    }
}

В данном случае вектор phones хранит варианты перечисления Phones, которое с помощью своих вариантов позволяет описать номера телефонов как в виде числа, так и в виде строки.

Консольный вывод:

19876542310
1-987-654-3210

Длина вектора

Длина вектора возвращается методом len():

fn main(){
  
    let mut users = vec!["Tom", "Sam", "Bob"];
	println!("length: {}", users.len());	// 3
}

Удаление элементов

Для удаления элемента по индексу применяется метод remove():

fn main(){
  
    let mut users = vec!["Tom", "Sam", "Bob", "Alice"];
	let removed_element = users.remove(1);	 // удаляем второй элемент
	
	println!("Удаленный элемент: {}", removed_element);	// Sam
	for user in &users{
		print!("{} ", user);	// Tom Bob Alice
	}
}

Еще один метод - pop() удаляет элемент из конца вектора и возвращает его:

fn main(){
  
    let mut users = vec!["Tom", "Sam", "Bob", "Alice"];
	let removed_element = users.pop();	 // удаляем элемент из конца
	
	println!("Удаленный элемент: {}", removed_element.unwrap());	// Alice
	for user in &users{
		print!("{} ", user);	// Tom Sam Bob
	}
}

В реальности метод pop() возвращает объект перечисления Option<T>. И чтобы получить из него содержащийся в нем элемент, применяется метод unwrap(): removed_element.unwrap().

Это только часть функционала векторов. Все методы вектора можно посмотреть в документации - https://doc.rust-lang.org/std/vec/struct.Vec.html

Помощь сайту
Юмани:
410011174743222
Номер карты:
4048415020898850