Кортежи в Rust
Кортеж (tuple) объединяет несколько значений — возможно, разных типов — в одно. Это быстрый способ собрать вместе связанные данные, не объявляя для них целый новый тип.
Создаём кортеж
Заголовок раздела «Создаём кортеж»fn main() { let person: (&str, i32, bool) = ("Алиса", 30, true); println!("{:?}", person);}Вывод:
("Алиса", 30, true)Обрати внимание: в одном кортеже смешаны &str, i32 и bool. Массив так не умеет — все его элементы должны быть одного типа.
Чтобы вывести кортеж целиком, используй {:?} (отладочный формат), а не {}. Обычный {} для кортежей не работает.
Доступ к элементам по позиции
Заголовок раздела «Доступ к элементам по позиции»Используй .0, .1, .2 и так далее — позиции в кортеже считаются с нуля:
fn main() { let person = ("Алиса", 30, true);
println!("Имя: {}", person.0); println!("Возраст: {}", person.1); println!("Активна: {}", person.2);}Вывод:
Имя: АлисаВозраст: 30Активна: trueПозиция должна быть обычным числом, записанным прямо в коде. Переменную вроде person.i использовать нельзя: на каждой позиции может лежать значение своего типа, а Rust должен знать тип ещё при компиляции.
Деструктуризация кортежа
Заголовок раздела «Деструктуризация кортежа»Все значения можно разложить по отдельным переменным одной строкой:
fn main() { let person = ("Алиса", 30, true); let (name, age, is_active) = person;
println!("{name}, {age} лет, активна: {is_active}");}Вывод:
Алиса, 30 лет, активна: trueЧасто это понятнее, чем раз за разом писать .0, .1, .2, особенно когда полей в кортеже больше двух-трёх.
Возвращаем из функции несколько значений
Заголовок раздела «Возвращаем из функции несколько значений»Кортежи — естественный способ вернуть больше одного значения, ведь функция может объявить только один тип результата:
fn min_max(numbers: &[i32]) -> (i32, i32) { let min = *numbers.iter().min().unwrap(); let max = *numbers.iter().max().unwrap(); (min, max)}
fn main() { let (smallest, largest) = min_max(&[4, 8, 1, 9, 3]); println!("минимум: {smallest}, максимум: {largest}");}Вывод:
минимум: 1, максимум: 9Пока не обращай внимания на &[i32], .iter() и .unwrap() — о них рассказано в уроках Срезы, Итераторы и Option. Здесь важно другое: тип результата (i32, i32) и то, как main сразу раскладывает его на две переменные.
Меняем кортежи и обмениваем значения
Заголовок раздела «Меняем кортежи и обмениваем значения»Если кортеж объявлен с mut, его поля можно менять по одному через те же позиции .0, .1. Значения меняться могут, а форма — нет: у кортежа всегда остаются то же число полей и те же типы, с которыми он появился.
Деструктуризация работает и в обратную сторону: можно присваивать в уже существующие переменные. Так получается аккуратный обмен значений в одну строку, без временной переменной. Если нужны не все поля, напиши _ вместо ненужных:
fn main() { let mut point = (3, 4); point.0 += 10; // меняем одно поле на месте println!("точка: {:?}", point);
let mut a = 1; let mut b = 2; (a, b) = (b, a); // меняем значения местами println!("a = {a}, b = {b}");
let (x, _) = point; // берём x, второе поле пропускаем println!("x = {x}");}Вывод:
точка: (13, 4)a = 2, b = 1x = 13Обмен работает потому, что Rust сначала собирает правую часть, (b, a), и только потом раскладывает её в a и b. Гораздо больше возможностей _ и образцов — в уроке Сопоставление с образцом.
Пустой кортеж: ()
Заголовок раздела «Пустой кортеж: ()»На самом деле ты уже встречал кортеж, сам того не замечая. () называется единичным типом (unit type), и именно его возвращает функция, которая не возвращает ничего осмысленного. Это неявный тип результата fn main().
Кортеж или структура
Заголовок раздела «Кортеж или структура»Кортежи отлично подходят для быстрых безымянных группировок — особенно чтобы вернуть из функции два-три значения. Когда же группа значений описывает настоящее понятие с именованными полями (например, Person с именем и возрастом, к которым ты по всему коду обращаешься по имени), обычно лучше и понятнее структура.
Практика
Заголовок раздела «Практика»1. Сумма и произведение
Заголовок раздела «1. Сумма и произведение»Напиши функцию sum_and_product(a: i32, b: i32) -> (i32, i32), которая возвращает и сумму, и произведение двух чисел. В main вызови её с 5 и 6 и деструктурируй результат. Подсказка: последняя строка функции — сам кортеж (a + b, a * b), без точки с запятой.
Показать решение
fn sum_and_product(a: i32, b: i32) -> (i32, i32) { (a + b, a * b)}
fn main() { let (sum, product) = sum_and_product(5, 6); println!("сумма = {sum}, произведение = {product}");}Вывод:
сумма = 11, произведение = 302. Прямоугольник по двум углам
Заголовок раздела «2. Прямоугольник по двум углам»Прямоугольник можно хранить как кортеж из двух точек, где каждая точка — тоже кортеж: let rect = ((1, 2), (5, 5));. Вычисли его ширину, высоту и площадь. Подсказка: кортежи можно вкладывать друг в друга, и rect.1.0 значит «первое поле второй точки».
Показать решение
fn main() { let rect = ((1, 2), (5, 5));
let width = rect.1.0 - rect.0.0; let height = rect.1.1 - rect.0.1;
println!("ширина {width}, высота {height}, площадь {}", width * height);}Вывод:
ширина 4, высота 3, площадь 12Можно сразу деструктурировать обе точки: let ((x1, y1), (x2, y2)) = rect;. Печатать дольше, зато x2 - x1 читается легче, чем rect.1.0 - rect.0.0. Когда точки начинают громоздиться вот так, это знак, что структура с именованными полями была бы понятнее.
- Кортеж объединяет значения, возможно, разных типов:
(name, age, is_active). - К полям обращаются через
.0,.1,.2или разом раскладывают их деструктуризацией. - Кортежи — естественный способ вернуть из функции несколько значений.
- Поля
mut-кортежа можно менять, а его размер и типы — нет.(a, b) = (b, a);меняет два значения местами. - Для именованных группировок, которые используются много раз, бери структуру.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Что может хранить кортеж, чего не может массив?
2. Как обратиться ко второму элементу кортежа person?
3. Чему равны a и b после let mut a = 1; let mut b = 2; (a, b) = (b, a);?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- Кортежи — книга «Язык программирования Rust»: официальное введение — как создавать кортежи, деструктурировать их и обращаться к элементам по индексу (перевод на русский).
- Кортежи — Rust на примерах: запускаемые примеры с вложенными кортежами и кортежами в аргументах функций (перевод на русский).
- The
tupletype — документация стандартной библиотеки (на английском): какие трейты поддерживают кортежи — сравнение, копирование и вывод через{:?}. - The
unittype — документация стандартной библиотеки (на английском): что такое()и где он встречается. - Destructuring assignments — The Rust Reference (на английском): точные правила, на которых держится
(a, b) = (b, a);.