% Структуры
Структуры (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);
}
Вы можете включить в описание структуры ..
чтобы показать, что вы хотите
использовать значения полей какой-то другой структуры. Например:
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
.
Вы можете объявить структуру без полей вообще:
struct Electron;
let x = Electron;
Такие структуры называют «unit-подобные» («unit-like»), потому что они похожи
на пустой кортеж ()
, иногда называемый «unit». Как и кортежные структуры, их
называют новым типом.
Сами по себе они редко бывают полезны (хотя иногда их используют в качестве меток), но в сочетании с другими возможностями их использование имеет смысл. Например, для использования библиотеки может быть необходимо создать структуру, которая реализует определенный типаж для обработки событий. Если у вас нет данных, которые нужно поместить в структуру, то можно просто создать unit-подобную структуру.