Структуры

Структуры (struct) — это один из способов создания более сложных типов данных. Например, если мы рассчитываем что-то с использованием координат 2D пространства, то нам понадобятся оба значения — x и y:

let origin_x = 0;
let origin_y = 0;

Структура позволяет нам объединить эти два значения в один тип с x и y в качестве имен полей:

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let origin = Point { x: 0, y: 0 }; // origin: Point

    println!("Начало координат находится в ({}, {})", origin.x, origin.y);
}

Этот код делает много разных вещей, поэтому давайте разберём его по порядку. Мы объявляем структуру с помощью ключевого слова struct, за которым следует имя объявляемой структуры. Обычно, имена типов-структур начинаются с заглавной буквы и используют чередующийся регистр букв: название PointInSpace выглядит привычно, а Point_In_Space — нет.

Как всегда, мы можем создать экземпляр нашей структуры с помощью оператора let. Однако в данном случае мы используем синтаксис вида ключ: значение для установки значения каждого поля. Порядок инициализации полей не обязательно должен совпадать с порядком их объявления.

Наконец, поскольку у полей есть имена, мы можем получить к ним доступ с помощью операции точка: origin.x.

Значения, хранимые в структурах, неизменяемы по умолчанию. В этом плане они не отличаются от других именованных сущностей. Чтобы они стали изменяемы, используйте ключевое слово mut:

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let mut point = Point { x: 0, y: 0 };

    point.x = 5;

    println!("Точка находится в ({}, {})", point.x, point.y);
}

Этот код напечатает Точка находится в (5, 0).

Rust не поддерживает изменяемость отдельных полей, поэтому вы не можете написать что-то вроде такого:

struct Point {
    mut x: i32,
    y: i32,
}

Изменяемость — это свойство имени, а не самой структуры. Если вы привыкли к управлению изменяемостью на уровне полей, сначала это может показаться непривычным, но на самом деле такое решение сильно упрощает вещи. Оно даже позволяет вам делать имена изменяемыми только на короткое время:

struct Point {
    x: i32,
    y: i32,
}

fn main() {
    let mut point = Point { x: 0, y: 0 };

    point.x = 5;

    let point = point; // это новое имя неизменяемо

    point.y = 6; // это вызывает ошибку
}

Структуры так же могут содержать &mut ссылки, это позволяет вам производить подобные преобразования:

struct Point {
    x: i32,
    y: i32,
}

struct PointRef<'a> {
    x: &'a mut i32,
    y: &'a mut i32,
}

fn main() {
    let mut point = Point { x: 0, y: 0 };

    {
        let r = PointRef { x: &mut point.x, y: &mut point.y };

        *r.x = 5;
        *r.y = 6;
    }

    assert_eq!(5, point.x);
    assert_eq!(6, point.y);
}

Синтаксис обновления (update syntax)

Вы можете включить в описание структуры .. чтобы показать, что вы хотите использовать значения полей какой-то другой структуры. Например:

struct Point3d {
    x: i32,
    y: i32,
    z: i32,
}

let mut point = Point3d { x: 0, y: 0, z: 0 };
point = Point3d { y: 1, .. point };

Этот код присваивает point новое y, но оставляет старые x и z. Это не обязательно должна быть та же самая структура — вы можете использовать этот синтаксис когда создаёте новые структуры, чтобы скопировать значения неуказанных полей:

# struct Point3d {
#     x: i32,
#     y: i32,
#     z: i32,
# }
let origin = Point3d { x: 0, y: 0, z: 0 };
let point = Point3d { z: 1, x: 2, .. origin };

Кортежные структуры

В Rust есть ещё один тип данных, который представляет собой нечто среднее между кортежем и структурой. Он называется кортежной структурой. Кортежные структуры именуются, а вот у их полей имён нет:

struct Color(i32, i32, i32);
struct Point(i32, i32, i32);

let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);

Эти два объекта различны, несмотря на то, что у них одинаковые значения.

Почти всегда, вместо кортежной структуры лучше использовать обычную структуру. Мы бы скорее объявили типы Color и Point вот так:

struct Color {
    red: i32,
    blue: i32,
    green: i32,
}

struct Point {
    x: i32,
    y: i32,
    z: i32,
}

Хорошие имена важны, и хотя значения в кортежной структуре могут быть так же получены с помощью операции точка, структуры дают нам настоящее имя, а не позицию.

Однако, есть один случай, когда кортежные структуры очень полезны. Это кортежная структура с всего одним элементом. Такое использование называется новым типом, потому что оно позволяет создать новый тип, отличный от типа значения, содержащегося в кортежной структуре. При этом новый тип обозначает что-то другое:

struct Inches(i32);

let length = Inches(10);

let Inches(integer_length) = length;
println!("Длина в дюймах: {}", integer_length);

Как вы можете видеть в данном примере, извлечь вложенный целый тип можно с помощью деконструирующего let. Мы обсуждали это выше, в разделе «кортежи». В данном случае, оператор let Inches(integer_length) присваивает 10 имени integer_length.

Unit-подобные структуры

Вы можете объявить структуру без полей вообще:

struct Electron;

let x = Electron;

Такие структуры называют «unit-подобные» («unit-like»), потому что они похожи на пустой кортеж (), иногда называемый «unit». Как и кортежные структуры, их называют новым типом.

Сами по себе они редко бывают полезны (хотя иногда их используют в качестве меток), но в сочетании с другими возможностями их использование имеет смысл. Например, для использования библиотеки может быть необходимо создать структуру, которая реализует определенный типаж для обработки событий. Если у вас нет данных, которые нужно поместить в структуру, то можно просто создать unit-подобную структуру.