Кортежи
Как мы знаем, массив в Rust — это последовательность заранее известного размера, содержащая элементы одинакового типа. Массивы хорошо подходят для хранения таких сущностей, как, например, координаты в пространстве: [x, y, z] — все три составляющие координаты имеют одинаковый тип. Но что делать, если для хранения какой-то сущности требуется последовательность элементов разного типа?
Например, мы разрабатываем картотеку для отдела кадров, и по каждому сотруднику нам необходимо хранить его полное имя, дату рождения и пометку, является ли сотрудник действующим, — всё это данные разных типов: String, u32 и bool. Решить нашу задачу могут помочь кортежи.
Кортеж — это последовательность заранее известного размера, которая может содержать элементы разных типов.
Tip
Программисты на Java могут провести аналогию с классами
Pair<T1,T2>иTriplet<T1,T2,T3>из библиотеки Apache Commons.
Синтаксис объявления кортежа выглядит так:
(значение_1, значение_2, …, значение_N)
Пример: кортеж для хранения имени сотрудника, даты его рождения и пометки, является ли он действующим сотрудником.
#![allow(unused)]
fn main() {
let employee: (&str, i32, bool) = ("John Doe", 1980, true);
}
Доступ к элементам кортежа осуществляется при помощи . после которой следует индекс элемента (индексация с нуля).
#![allow(unused)]
fn main() {
println!(
"Name: {}, birth year: {}, active: {}",
employee.0, employee.1, employee.2
);
}
Также для кортежей есть удобный синтаксис, который позволяет за раз “разложить” весь кортеж на элементы и присвоить эти элементы переменным.
#![allow(unused)]
fn main() {
let (name, birth_year, is_active) = employee;
}
Note
Такая операция “разложения” на составляющие называется деструктурирующим присваиванием. О ней мы отдельно поговорим в главе Деструктурирование
Возврат кортежа из функции
Одно из удобных применений кортежа — возврат нескольких значений из функции.
Например, нам нужна функция, которая принимает в качестве аргумента последовательность чисел и разделяет её на две части: первая содержит все нечётные числа, вторая — все чётные.
fn split_to_odd_and_even(numbers: &[i32]) -> (Vec<i32>, Vec<i32>) {
let mut odds = Vec::new(); // для нечётных
let mut evens = Vec::new(); // для чётных
for n in numbers {
if n % 2 != 0 {
odds.push(*n);
} else {
evens.push(*n);
}
}
(odds, evens)
}
fn main() {
let numbers = vec![1,2,3,4,5,6,7,8,9];
let (odds, evens) = split_to_odd_and_even(&numbers); // получаем слайс на вектор
println!("Odd numbers: {odds:?}");
println!("Even numbers: {evens:?}");
}
Как мы видим, здесь в теле функции split_to_odd_and_even мы создали два вектора и оба вернули из функции путём “заворачивания” в кортеж.
Также заметим, что функция split_to_odd_and_even в качестве аргумента ожидает слайс, поэтому при помощи оператора & мы создаём слайс, указывающий на элементы вектора. Такая передача последовательности в функцию посредством слайса — одна из распространённых практик в Rust. Это позволяет вызывать функцию как для вектора, так и для массива.
Например, так бы выглядел вариант с массивом:
fn main() {
let numbers = [1,2,3,4,5,6,7,8,9];
let (odds, evens) = split_to_odd_and_even(&numbers); // получаем слайс на массив
println!("Odd numbers: {odds:?}");
println!("Even numbers: {evens:?}");
}