Структуры

Структура — это другой вид агрегатного типа, как и кортеж. Разница в том, что в структурах у каждого элемента есть имя. Элемент структуры называется полем или членом структуры. Смотрите:

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; // это вызывает ошибку
}

Синтаксис обновления (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);

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

# 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.