Перейти к содержимому

Уроки · 37

Свои структуры

В уроке «Разноцветные кружки» каждый кружок — это кортеж из трёх элементов: (f32, f32, Color). Добавим кружкам ещё и радиус — четвёртый элемент, (f32, f32, f32, Color).

У элементов кортежа есть номера — .0, .1, .2, .3 — и достать элемент по номеру можно прямо через точку:

let circle: (f32, f32, f32, Color) = (120.0, 80.0, 30.0, YELLOW);
// .0, .1, .2, .3 — элементы кортежа по порядку.
draw_circle(circle.0, circle.1, circle.2, circle.3);

Можно вместо этого разобрать кортеж на отдельные переменные — так, как мы всегда делали с mouse_position():

let (x, y, radius, color) = circle;
draw_circle(x, y, radius, color);

Читается лучше, но разбирать приходится заново на каждом месте, где кортеж используется — а в уроке «Список кружков» таких мест несколько: и когда кружок только создаётся, и в цикле, где рисуется каждый уже сохранённый. С парой чисел это было не в тягость. С четырьмя элементами — уже утомительно, и легко перепутать местами два поля одного типа при разборе: например, radius и x, оба f32, компилятор такую ошибку даже не заметит.

Хочется чего-то среднего: чтобы поля были видны по имени, но разбирать кортеж заново каждый раз не приходилось — сразу пользоваться именем поля, как у массива есть индекс, доступный прямо на месте, без подготовки. Кортеж для этого не подходит: у него просто нет имён для полей, только номера. В Rust имена полей нельзя придумать на ходу, прямо в месте, где создаётся значение — сначала нужно объявить форму структуры отдельно, один раз, а уже потом создавать по ней значения:

struct Circle {
x: f32,
y: f32,
radius: f32,
color: Color,
}

Разберём по кусочкам. struct — ключевое слово, как let и fn: Rust понимает его как «дальше будет новый тип данных», и это слово тоже зарезервировано языком, не наше. После него — имя типа, здесь Circle (по соглашению с большой буквы, в отличие от имён переменных и функций). А в фигурных скобках — поля: у каждого сначала имя, потом через двоеточие тип, через запятую, если полей несколько, — та же запись имя: тип, что и у параметров функции в уроке «Свои функции», только здесь она описывает не вход функции, а форму будущих значений.

Значение такого типа создаётся по имени, с явным указанием, что куда идёт:

let circle = Circle { x: 120.0, y: 80.0, radius: 30.0, color: YELLOW };
// Каждое поле теперь видно по имени, а не по номеру.
draw_circle(circle.x, circle.y, circle.radius, circle.color);

Color тут — тот же тип, что и в уроке «Разноцветные кружки»: тип значений вроде YELLOW и RED.

Кликай по экрану — на месте клика появляется кружок со случайным радиусом и случайным цветом из того же набора, что и в уроке «Разноцветные кружки».

Замени содержимое src/main.rs на это:

src/main.rs
use macroquad::prelude::*;
// Фиксированный список цветов, из которого выбираем случайный.
const COLORS: [Color; 4] = [YELLOW, RED, GREEN, BLUE];
// Один кружок: положение, радиус и цвет вместе, под понятными именами.
struct Circle {
x: f32,
y: f32,
radius: f32,
color: Color,
}
#[macroquad::main("Свои структуры")]
async fn main() {
// Список уже нарисованных кружков — сначала пустой.
let mut circles: Vec<Circle> = Vec::new();
loop {
clear_background(BLACK);
// Клик — новый кружок со случайным радиусом и случайным цветом.
if is_mouse_button_pressed(MouseButton::Left) {
let (x, y) = mouse_position();
let radius = rand::gen_range(10.0, 60.0);
let color = COLORS[rand::gen_range(0, COLORS.len())];
circles.push(Circle { x, y, radius, color });
}
// Рисуем все уже сохранённые кружки.
for circle in &circles {
draw_circle(circle.x, circle.y, circle.radius, circle.color);
}
next_frame().await;
}
}

Запусти как обычно:

Окно терминала
cargo run

Попробуй прямо здесь:

  • struct Circle { ... } объявляет новый тип данных с именованными полями — свою собственную структуру, а не встроенную, как f32 или Color.
  • Circle { x: ..., y: ..., radius: ..., color: ... } создаёт значение этого типа — поля перечисляются по имени, порядок не важен.
  • circle.radius, circle.color — доступ к полю по имени, вместо .0, .1, .2, .3 у кортежа, и без того, чтобы сначала разбирать всю структуру на отдельные переменные — поле берётся прямо там, где нужно.
  • radius в Circle { ..., radius, ... } — сокращённая запись: раз локальная переменная называется так же, как поле, radius: radius можно не писать целиком.
  • rand::gen_range(10.0, 60.0) — случайный радиус от 10 до 60, та же функция, что уже выбирала случайный цвет и случайную точку («Случайная точка»), только диапазон теперь не индекс массива, а сам радиус.
  • Vec<Circle> — тот же растущий список из урока «Список кружков», просто элементы теперь — не кортежи, а значения Circle.