Для хранения набора однотипных данных в языке 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