Структуры в Rust
Структура (struct) собирает связанные значения в один именованный тип. Кортеж — это быстрая связка без имён, а в структуре у каждого поля есть имя. Код, который с ней работает, читается гораздо легче.
Объявляем и создаём структуру
Заголовок раздела «Объявляем и создаём структуру»struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Алиса"), age: 30, };
println!("{}: {} лет", alice.name, alice.age);}Вывод:
Алиса: 30 летСравни с кортежем ("Алиса", 30): у структуры alice.name и alice.age говорят сами за себя. Не нужно помнить, что поле 0 — это имя, а поле 1 — возраст.
Изменяемая структура
Заголовок раздела «Изменяемая структура»Как и переменные, экземпляры структур по умолчанию неизменяемые. Добавь mut, чтобы менять поле после создания:
struct Person { name: String, age: u32,}
fn main() { let mut alice = Person { name: String::from("Алиса"), age: 30, };
alice.age += 1; println!("{} теперь {} лет", alice.name, alice.age);}Добавляем поведение: методы
Заголовок раздела «Добавляем поведение: методы»К структуре можно привязать функции с помощью блока impl. Внутри него &self означает тот самый экземпляр, у которого вызвали метод:
struct Person { name: String, age: u32,}
impl Person { fn greet(&self) { println!("Привет, я {}, мне {} лет", self.name, self.age); }}
fn main() { let alice = Person { name: String::from("Алиса"), age: 30 }; alice.greet();}Вывод:
Привет, я Алиса, мне 30 летФункции-конструкторы
Заголовок раздела «Функции-конструкторы»Частый приём — функция new внутри impl, которая собирает экземпляр за тебя. Тогда тому, кто её вызывает, не нужно вручную перечислять все поля:
struct Person { name: String, age: u32,}
impl Person { fn new(name: &str, age: u32) -> Person { Person { name: name.to_string(), age } }}
fn main() { let bob = Person::new("Боб", 25); println!("{}: {} лет", bob.name, bob.age);}Обрати внимание на одинокое age в Person { name: name.to_string(), age }. Если у переменной точно такое же имя, как у поля, можно не писать age: age, а написать просто age. В Rust это называется сокращённой инициализацией полей (field init shorthand).
Выводим структуру через derive
Заголовок раздела «Выводим структуру через derive»Попробуй написать println!("{:?}", alice) для структуры из примеров выше — код не скомпилируется. Rust не знает, как выводить твой собственный тип для отладки, пока ты его не попросишь. Добавь #[derive(Debug)] над структурой, и это умение появится бесплатно:
#[derive(Debug)]struct Person { name: String, age: u32,}
fn main() { let alice = Person { name: String::from("Алиса"), age: 30 }; println!("{:?}", alice);}Вывод:
Person { name: "Алиса", age: 30 }#[derive(...)] — это способ Rust автоматически сгенерировать для типа типичную функциональность. Ты постоянно будешь видеть, как так добавляют Debug, а позже Clone и другие трейты.
Практика
Заголовок раздела «Практика»1. Методы прямоугольника
Заголовок раздела «1. Методы прямоугольника»Создай структуру Rectangle с полями width и height (u32). Дай ей два метода: area, который возвращает площадь, и is_square, который возвращает true, когда стороны равны. Подсказка: оба метода только читают поля, поэтому принимают &self.
Показать решение
struct Rectangle { width: u32, height: u32,}
impl Rectangle { fn area(&self) -> u32 { self.width * self.height }
fn is_square(&self) -> bool { self.width == self.height }}
fn main() { let wide = Rectangle { width: 8, height: 3 }; let square = Rectangle { width: 5, height: 5 };
println!("широкий: площадь {}, квадрат? {}", wide.area(), wide.is_square()); println!("квадрат: площадь {}, квадрат? {}", square.area(), square.is_square());}Вывод:
широкий: площадь 24, квадрат? falseквадрат: площадь 25, квадрат? true2. Банковский счёт
Заголовок раздела «2. Банковский счёт»Создай структуру Account с полями owner (String) и balance — баланс в копейках (u32). Напиши Account::new("Аня"), которая начинает с баланса 0. Затем положи на счёт 50 и 25 копеек, меняя поле, и выведи счёт двумя способами: обычным предложением и через {:?}. Подсказка: чтобы менять account.balance, переменная должна быть let mut, а для {:?} нужен #[derive(Debug)].
Показать решение
#[derive(Debug)]struct Account { owner: String, balance: u32,}
impl Account { fn new(owner: &str) -> Account { Account { owner: owner.to_string(), balance: 0 } }}
fn main() { let mut account = Account::new("Аня"); account.balance += 50; account.balance += 25;
println!("У владельца {} на счёте {} коп.", account.owner, account.balance); println!("{:?}", account);}Вывод:
У владельца Аня на счёте 75 коп.Account { owner: "Аня", balance: 75 }new сама ставит balance: 0, так что вызывающий код не может об этом забыть. Во многом поэтому функции-конструкторы так популярны.
3. Список людей
Заголовок раздела «3. Список людей»Положи три значения Person в вектор: Аня (34), Бен (16) и Чэнь (52). Выведи имена всех, кому 18 или больше, а затем — кто самый старший. Подсказка: перебирай людей циклом for person in &people и запоминай самого старшего на данный момент через let mut oldest = &people[0];.
Показать решение
struct Person { name: String, age: u32,}
fn main() { let people = vec![ Person { name: String::from("Аня"), age: 34 }, Person { name: String::from("Бен"), age: 16 }, Person { name: String::from("Чэнь"), age: 52 }, ];
let mut oldest = &people[0]; for person in &people { if person.age >= 18 { println!("{} — взрослый человек", person.name); } if person.age > oldest.age { oldest = person; } }
println!("Самый старший: {} ({})", oldest.name, oldest.age);}Вывод:
Аня — взрослый человекЧэнь — взрослый человекСамый старший: Чэнь (52)oldest — это ссылка (&Person) на элемент вектора, а не копия, так что ничего не клонируется. Каждый раз, когда находится кто-то старше, мы просто перенаправляем oldest на него.
- Структура собирает именованные поля в один тип:
struct Person { name: String, age: u32 }. - Экземпляр создаётся так:
Person { name: ..., age: ... }. Отметь егоmut, чтобы потом менять поля. - Поведение добавляется блоком
impl;&self— это экземпляр, у которого вызвали метод. - Добавь
#[derive(Debug)], чтобы структуру можно было вывести через{:?}.
Проверь себя
Заголовок раздела «Проверь себя»Проверь себя
1. Главное преимущество структуры перед кортежем?
2. Что даёт #[derive(Debug)] у структуры?
3. Что делает одинокое age в Person { name: name.to_string(), age }?
Результат: 0 / 3
Что почитать
Заголовок раздела «Что почитать»- Определение и создание экземпляров структур — книга «Язык программирования Rust»: ещё и синтаксис обновления структуры (
..other), кортежные структуры и структуры без полей (перевод на русский). - Пример программы, использующей структуры — книга «Язык программирования Rust»: программа про прямоугольник переделывается из отдельных переменных в кортеж, а потом в структуру — сразу видно, чем структуры помогают (перевод на русский).
- Синтаксис метода — книга «Язык программирования Rust»:
&selfи&mut self, методы с дополнительными параметрами и ассоциированные функции вродеnew(перевод на русский). - Структуры — Rust на примерах: запускаемые примеры всех видов структур и небольшое упражнение в конце (перевод на русский).
- Атрибут Derive — Rust на примерах: что
#[derive(...)]умеет генерировать за тебя, напримерDebug,CloneиPartialEq(перевод на русский).