Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Кортежи

Как мы знаем, массив в 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:?}");
}