Добро пожаловать в восьмую лекцию нашего курса по языку программирования Rust! Сегодня мы погрузимся в мир последовательностей — фундаментальных структур данных, которые позволяют хранить и обрабатывать наборы элементов. Мы рассмотрим три ключевые структуры: массивы, кортежи и векторы. Эта лекция будет подробной, самодостаточной и ориентированной как на новичков, так и на тех, кто хочет глубже понять тонкости Rust. Мы разберём всё шаг за шагом: от базового синтаксиса до сравнения производительности, итераций и практических примеров. В конце вас ждёт упражнение с разбором решения.
В программировании часто нужно работать с набором данных: списком чисел, набором строк или комбинацией разных типов. Rust предоставляет три основных типа последовательностей, каждый из которых решает свои задачи:
Если представить их как сумки: массивы — это пакет с фиксированным числом одинаковых конфет, кортежи — это ланч-бокс с разными продуктами (бутерброд, яблоко, сок), а векторы — это рюкзак, в который можно добавлять или убирать одинаковые вещи, например, книги.
Эти структуры лежат в основе многих программ, и понимание их различий и возможностей критично для написания эффективного кода на Rust.
[T; N]Массив в Rust — это структура данных с фиксированным размером, которая хранит элементы одного типа. Его синтаксис: [T; N], где:
T — тип элементов (например, i32 для целых чисел),N — количество элементов, известное на этапе компиляции.Массивы создаются с указанием всех элементов или с использованием шаблона для повторяющихся значений.
fn main() {
// Простой массив из трёх чисел
let numbers: [i32; 3] = [1, 2, 3];
// Массив с повторяющимся значением (5 элементов, все равны 0)
let zeros: [i32; 5] = [0; 5];
println!("Numbers: {:?}", numbers); // Вывод: [1, 2, 3]
println!("Zeros: {:?}", zeros); // Вывод: [0, 0, 0, 0, 0]
}
Обратите внимание:
let numbers = [1, 2, 3];).[значение; количество] удобен для инициализации одинаковыми значениями.Доступ к элементам массива осуществляется по индексу (начиная с 0):
fn main() {
let arr = [10, 20, 30];
println!("Первый элемент: {}", arr[0]); // 10
println!("Второй элемент: {}", arr[1]); // 20
}
Но будьте осторожны: попытка обратиться к несуществующему индексу (например, arr[3] для массива из трёх элементов) вызовет панику (ошибку времени выполнения). Позже мы узнаем про безопасные методы доступа.
(T1, T2, ...)Кортеж — это структура с фиксированным размером, которая может содержать элементы разных типов. Синтаксис: (T1, T2, ...).
Кортеж создаётся с помощью круглых скобок:
fn main() {
let tuple: (i32, f64, &str) = (42, 3.14, "hello");
println!("Кортеж: {:?}", tuple);
}
Типы элементов могут быть любыми, и их количество фиксировано на этапе компиляции.
Доступ осуществляется через точку и индекс (начиная с 0):
fn main() {
let tuple = (42, 3.14, "hello");
println!("Первый: {}", tuple.0); // 42
println!("Второй: {}", tuple.1); // 3.14
println!("Третий: {}", tuple.2); // "hello"
}
Один из самых мощных инструментов работы с кортежами — деструктуризация. Она позволяет "разобрать" кортеж на отдельные переменные:
fn main() {
let tuple = (42, 3.14, "hello");
let (num, pi, text) = tuple;
println!("Число: {}, Пи: {}, Текст: {}", num, pi, text);
}
Если вам нужен только один элемент, остальные можно игнорировать с помощью _:
fn main() {
let tuple = (42, 3.14, "hello");
let (num, _, text) = tuple;
println!("Число: {}, Текст: {}", num, text);
}
Vec<T>Вектор (Vec<T>) — это динамическая структура данных, которая может расти или уменьшаться в размере. Это аналог списков или динамических массивов в других языках.
Векторы создаются с помощью макроса vec! или метода Vec::new():
fn main() {
// С помощью макроса vec!
let mut numbers: Vec = vec![1, 2, 3];
// С помощью Vec::new()
let mut empty: Vec = Vec::new();
println!("Numbers: {:?}", numbers); // [1, 2, 3]
println!("Empty: {:?}", empty); // []
}
Обратите внимание на mut: вектор должен быть изменяемым, чтобы добавлять или удалять элементы.
push и poppush — добавляет элемент в конец вектора.pop — удаляет и возвращает последний элемент (возвращает Option<T>).fn main() {
let mut vec = vec![1, 2];
vec.push(3); // Добавляем 3
println!("После push: {:?}", vec); // [1, 2, 3]
if let Some(value) = vec.pop() {
println!("Извлечён: {}", value); // 3
}
println!("После pop: {:?}", vec); // [1, 2]
}
Как и в массивах, доступ возможен по индексу, но есть безопасный метод get:
fn main() {
let vec = vec![10, 20, 30];
println!("Первый: {}", vec[0]); // 10
println!("Второй: {}", vec.get(1).unwrap()); // 20 (get возвращает Option)
}
vec[n] вызовет панику при выходе за границы.vec.get(n) возвращает Option<&T>, что безопаснее.| Тип | Размер | Тип данных | Хранение | Производительность | Использование |
|---|---|---|---|---|---|
| Массив | Фиксирован | Однотипные | Стек | Высокая | Статические данные |
| Кортеж | Фиксирован | Разнотипные | Стек | Высокая | Возврат нескольких значений |
| Вектор | Динамический | Однотипные | Куча | Средняя | Динамические списки |
[x, y, z]).(имя, возраст, рост)).Rust позволяет легко обходить элементы всех трёх типов с помощью циклов.
fn main() {
let arr = [1, 2, 3];
for x in arr {
println!("Элемент: {}", x);
}
}
Кортежи напрямую не итерируются, но можно использовать индексы или деструктуризацию для обработки.
fn main() {
let vec = vec![10, 20, 30];
for x in &vec { // &vec для неизменяемого доступа
println!("Элемент: {}", x);
}
}
Для изменения элементов в векторе используйте iter_mut():
fn main() {
let mut vec = vec![10, 20, 30];
for x in vec.iter_mut() {
*x *= 2; // Удваиваем каждый элемент
}
println!("Удвоенный вектор: {:?}", vec); // [20, 40, 60]
}
Найдём сумму элементов массива:
fn main() {
let arr = [1, 2, 3, 4];
let sum: i32 = arr.iter().sum();
println!("Сумма массива: {}", sum); // 10
}
.iter() — метод, который возвращает итератор по элементам массива/среза. Для массива он возвращает std::slice::Iter, который позволяет итерироваться по ссылкам на элементы (&i32).sum() — метод итератора, который суммирует все элементы, если их тип поддерживает сложение (например, i32 реализует трейт std::iter::Sum)..iter() — не только для массивов
Метод .iter() работает не только с массивами, но и с другими типами данных в Rust, которые предоставляют итерацию. Вот краткий комментарий:
[T; N]): .iter() возвращает итератор по ссылкам на элементы массива. Например, для [1, 2, 3] вы получите &1, &2, &3.&[T]): То же самое, что и для массивов, так как массив автоматически преобразуется в срез при вызове .iter().Vec<T>): .iter() возвращает итератор по элементам вектора (по ссылкам, &T).HashMap<K, V>): .iter() возвращает итератор по парам (&K, &V).IntoIterator или имеющая .iter(), может предоставлять итератор.Функция возвращает кортеж с результатами:
fn divide(a: i32, b: i32) -> (i32, i32) {
(a / b, a % b) // Целая часть и остаток
}
fn main() {
let (quotient, remainder) = divide(10, 3);
println!("Частное: {}, Остаток: {}", quotient, remainder); // 3, 1
}
Соберём чётные числа в вектор:
fn main() {
let mut vec = Vec::new();
for i in 0..10 {
if i % 2 == 0 {
vec.push(i);
}
}
println!("Чётные числа: {:?}", vec); // [0, 2, 4, 6, 8]
}
Задание: Напишите программу, которая:
[1, 2, 3, 4, 5]).Решение:
fn array_sum(arr: &[i32; 5]) -> i32 {
arr.iter().sum()
}
fn tuple_min_max(arr: &[i32; 5]) -> (i32, i32) {
let min = *arr.iter().min().unwrap();
let max = *arr.iter().max().unwrap();
(min, max)
}
fn even_numbers(arr: &[i32; 5]) -> Vec {
let mut vec = Vec::new();
for &num in arr {
if num % 2 == 0 {
vec.push(num);
}
}
vec
}
fn main() {
let numbers = [1, 2, 3, 4, 5];
// Массив: сумма
let sum = array_sum(&numbers);
println!("Сумма: {}", sum); // 15
// Кортеж: минимум и максимум
let (min, max) = tuple_min_max(&numbers);
println!("Минимум: {}, Максимум: {}", min, max); // 1, 5
// Вектор: чётные числа
let evens = even_numbers(&numbers);
println!("Чётные числа: {:?}", evens); // [2, 4]
}
Разбор:
&[i32; 5]), чтобы избежать перемещения.push.Мы подробно разобрали массивы, кортежи и векторы — три столпа работы с последовательностями в Rust. Теперь вы знаете их синтаксис, особенности, способы итерации и области применения. Практикуйтесь, экспериментируйте с кодом!