Добро пожаловать в восьмую лекцию нашего курса по языку программирования 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
и pop
push
— добавляет элемент в конец вектора.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. Теперь вы знаете их синтаксис, особенности, способы итерации и области применения. Практикуйтесь, экспериментируйте с кодом!