Визначення та створення екземплярів структур (Defining and Instantiating Structs)
Структури подібні до кортежів, про які йшлося в розділі “Тип кортежу”, тим, що обидва зберігають кілька пов’язаних значень. Як і кортежі, частини структури можуть мати різні типи. На відміну від кортежів, у структурі ви назвемо кожну частину даних, щоб було зрозуміло, що означають значення. Додавання цих назв означає, що структури є гнучкішими за кортежі: вам не потрібно покладатися на порядок даних, щоб визначати або отримувати доступ до значень екземпляра.
Щоб визначити структуру, ми вводимо ключове слово struct і називаємо всю структуру. Назва структури має описувати значущість частин даних, які групуються разом. Потім, усередині фігурних дужок, ми визначаємо назви та типи частин даних, які ми називаємо полями (fields). Наприклад, у Лістингу (Listing) 5-1 показано структуру, яка зберігає інформацію про обліковий запис користувача.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {}
Щоб використати структуру після того, як ми її визначили, ми створюємо екземпляр (instance) цієї структури, вказуючи конкретні значення для кожного з полів. Ми створюємо екземпляр, називаючи структуру, а потім додаючи фігурні дужки, що містять пари key: value, де ключі — це назви полів, а значення — це дані, які ми хочемо зберігати в цих полях. Нам не потрібно вказувати поля в тому самому порядку, в якому ми оголосили їх у структурі. Іншими словами, визначення структури — це як загальний шаблон для типу, а екземпляри заповнюють цей шаблон конкретними даними, щоб створити значення типу. Наприклад, ми можемо оголосити конкретного користувача, як показано в Лістингу (Listing) 5-2.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
}
Щоб отримати певне значення зі структури, ми використовуємо точкову нотацію. Наприклад, щоб отримати доступ до електронної адреси цього користувача, ми використовуємо user1.email. Якщо екземпляр є змінним, ми можемо змінити значення, використовуючи точкову нотацію і присвоюючи значення в конкретне поле. Лістинг (Listing) 5-3 показує, як змінити значення в полі email змінного екземпляра User.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
let mut user1 = User {
active: true,
username: String::from("someusername123"),
email: String::from("someone@example.com"),
sign_in_count: 1,
};
user1.email = String::from("anotheremail@example.com");
}
Зверніть увагу, що весь екземпляр має бути змінним; Rust не дозволяє нам позначати лише певні поля як змінні. Як і з будь-яким виразом, ми можемо побудувати новий екземпляр структури як останній вираз у тілі функції, щоб неявно повернути цей новий екземпляр.
Лістинг (Listing) 5-4 показує функцію build_user, яка повертає екземпляр User із заданими електронною адресою та ім’ям користувача. Поле active отримує значення true, а sign_in_count — значення 1.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn build_user(email: String, username: String) -> User {
User {
active: true,
username: username,
email: email,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("someone@example.com"),
String::from("someusername123"),
);
}
Має сенс називати параметри функції так само, як і поля структури, але необхідність повторювати назви полів і змінних email та username дещо обтяжлива. Якби структура мала більше полів, повторення кожної назви стало б ще більш набридливим. На щастя, є зручне скорочення!
Використання скороченого синтаксису ініціалізації полів (Using the Field Init Shorthand)
Оскільки в Лістингу (Listing) 5-4 імена параметрів і імена полів структури є точно однаковими, ми можемо використати синтаксис скороченого синтаксису ініціалізації полів для переписування build_user так, щоб вона поводилася точно так само, але без повторення username і email, як показано в Лістингу (Listing) 5-5.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn build_user(email: String, username: String) -> User {
User {
active: true,
username,
email,
sign_in_count: 1,
}
}
fn main() {
let user1 = build_user(
String::from("someone@example.com"),
String::from("someusername123"),
);
}
Тут ми створюємо новий екземпляр структури User, яка має поле з назвою email. Ми хочемо встановити значення поля email у значення параметра email функції build_user. Оскільки поле email і параметр email мають однакову назву, нам потрібно лише написати email, а не email: email.
Створення екземплярів за допомогою синтаксису оновлення структури (Creating Instances from Other Instances with Struct Update Syntax)
Часто корисно створити новий екземпляр структури, який включає більшість значень з іншого екземпляра того самого типу, але змінює деякі з них. Ви можете зробити це, використовуючи синтаксис оновлення структури.
Спочатку, у Лістингу (Listing) 5-6 ми покажемо, як створити новий екземпляр User у user2 звичайним способом, без синтаксису оновлення. Ми встановлюємо нове значення для email, але в іншому використовуємо ті самі значення з user1, які ми створили в Лістингу (Listing) 5-2.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
// --snip--
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
active: user1.active,
username: user1.username,
email: String::from("another@example.com"),
sign_in_count: user1.sign_in_count,
};
}
Використовуючи синтаксис оновлення структури, ми можемо досягти того самого ефекту з меншим обсягом коду, як показано в Лістингу (Listing) 5-7. Синтаксис .. указує, що решта полів, які не встановлено явно, мають мати ті самі значення, що й поля в заданому екземплярі.
struct User {
active: bool,
username: String,
email: String,
sign_in_count: u64,
}
fn main() {
// --snip--
let user1 = User {
email: String::from("someone@example.com"),
username: String::from("someusername123"),
active: true,
sign_in_count: 1,
};
let user2 = User {
email: String::from("another@example.com"),
..user1
};
}
Код у Лістингу (Listing) 5-7 також створює екземпляр у user2, який має інше значення для email, але має ті самі значення для полів username, active і sign_in_count з user1. ..user1 має бути останнім, щоб указати, що будь-які решта полів мають отримати свої значення з відповідних полів у user1, але ми можемо обрати вказати значення для будь-якої кількості полів у будь-якому порядку, незалежно від порядку полів у визначенні структури.
Зверніть увагу, що синтаксис оновлення структури використовує = як присвоювання; це тому, що він переміщує дані, так само як ми бачили в розділі “Змінні та дані, що взаємодіють із переміщенням”. У цьому прикладі ми більше не можемо використовувати user1 після створення user2, тому що String у полі username з user1 було переміщено в user2. Якби ми надали user2 нові значення String для обох полів email і username, і таким чином використали б лише значення active і sign_in_count з user1, тоді user1 усе ще був би дійсним після створення user2. І active, і sign_in_count — це типи, які реалізують трейт Copy, тому поведінка, яку ми обговорювали в розділі “Дані лише у стеку: Copy”, буде застосовуватися. Ми також усе ще можемо використовувати user1.email у цьому прикладі, тому що його значення не було переміщено з user1.
Створення різних типів за допомогою кортежних структур (Using Tuple Structs without Named Fields to Create Different Types)
Rust також підтримує структури, які виглядають подібно до кортежів, що називаються кортежними структурами (tuple structs). Кортежні структури мають додаткове значення, яке надає назва структури, але не мають назв, пов’язаних із їхніми полями; натомість вони просто мають типи полів. Кортежні структури корисні, коли ви хочете надати всьому кортежу назву і зробити кортеж іншим типом від інших кортежів, а також коли називати кожне поле, як у звичайній структурі, було б багатослівно або надлишково.
Щоб визначити кортежну структуру, почніть із ключового слова struct і назви структури, після чого вкажіть типи в кортежі. Наприклад, тут ми визначаємо та використовуємо дві кортежні структури з назвами Color і Point:
struct Color(i32, i32, i32);
struct Point(i32, i32, i32);
fn main() {
let black = Color(0, 0, 0);
let origin = Point(0, 0, 0);
}
Зверніть увагу, що значення black і origin мають різні типи, тому що це екземпляри різних кортежних структур. Кожна визначена вами структура є окремим типом, навіть якщо поля всередині структури можуть мати ті самі типи. Наприклад, функція, яка приймає параметр типу Color, не може приймати Point як аргумент, навіть якщо обидва типи складаються з трьох значень i32. В іншому випадку екземпляри кортежних структур подібні до кортежів тим, що ви можете розпакувати їх на окремі частини, і ви можете використовувати . із наступним індексом, щоб отримати доступ до окремого значення. На відміну від кортежів, кортежні структури вимагають, щоб ви назвали тип структури, коли розпаковуєте їх. Наприклад, ми б написали let Point(x, y, z) = origin;, щоб розпакувати значення в точці origin у змінні з назвами x, y і z.
Визначення одноподібних структур (Defining Unit-Like Structs without Any Fields)
Ви також можете визначати структури, які не мають жодних полів! Вони називаються одноподібними структурами (unit-like structs), тому що поводяться подібно до (), типу одиниці, про який ми згадували в розділі “Тип кортежу”. Одноподібні структури можуть бути корисними, коли вам потрібно реалізувати трейт для якогось типу, але ви не маєте жодних даних, які хочете зберігати в самому типі. Ми обговоримо трейти в Розділі 10. Ось приклад оголошення і створення екземпляра одноподібної структури з назвою AlwaysEqual:
struct AlwaysEqual;
fn main() {
let subject = AlwaysEqual;
}
Щоб визначити AlwaysEqual, ми використовуємо ключове слово struct, назву, яку хочемо, а потім крапку з комою. Не потрібно жодних фігурних дужок або круглих дужок! Потім ми можемо отримати екземпляр AlwaysEqual у змінній subject подібним способом: використовуючи назву, яку ми визначили, без будь-яких фігурних дужок або круглих дужок. Уявіть, що пізніше ми реалізуємо поведінку для цього типу так, що кожен екземпляр AlwaysEqual завжди дорівнює кожному екземпляру будь-якого іншого типу, можливо, щоб мати відомий результат для цілей тестування. Нам не потрібні були б жодні дані, щоб реалізувати таку поведінку! У Розділі 10 ви побачите, як визначати трейти і реалізовувати їх для будь-якого типу, включно з одноподібними структурами.
У визначенні структури
Userу Лістингу (Listing) 5-1 ми використовували типString, що володіє даними, а не тип рядкового зрізу&str. Це свідомий вибір, тому що ми хочемо, щоб кожен екземпляр цієї структури володів усіма своїми даними і щоб ці дані були дійсними доти, доки дійсна вся структура.Також можливо, щоб структури зберігали посилання на дані, які належать чомусь іншому, але для цього потрібно використати часи життя (lifetimes), особливість Rust, яку ми обговоримо в Розділі 10. Часи життя гарантують, що дані, на які посилається структура, є дійсними доти, доки дійсна структура. Припустімо, ви намагаєтеся зберегти посилання в структурі без указання часів життя, як у наведеному нижче в src/main.rs; це не спрацює:
struct User { active: bool, username: &str, email: &str, sign_in_count: u64, } fn main() { let user1 = User { active: true, username: "someusername123", email: "someone@example.com", sign_in_count: 1, }; }Компілятор скаржитиметься, що йому потрібні специфікатори часу життя:
$ cargo run Compiling structs v0.1.0 (file:///projects/structs) error[E0106]: missing lifetime specifier --> src/main.rs:3:15 | 3 | username: &str, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 1 ~ struct User<'a> { 2 | active: bool, 3 ~ username: &'a str, | error[E0106]: missing lifetime specifier --> src/main.rs:4:12 | 4 | email: &str, | ^ expected named lifetime parameter | help: consider introducing a named lifetime parameter | 1 ~ struct User<'a> { 2 | active: bool, 3 | username: &str, 4 ~ email: &'a str, | For more information about this error, try `rustc --explain E0106`. error: could not compile `structs` (bin "structs") due to 2 previous errorsУ Розділі 10 ми обговоримо, як виправити ці помилки, щоб ви могли зберігати посилання в структурах, але наразі ми виправлятимемо такі помилки, використовуючи типи, що володіють даними (owned types), як-от
String, замість посилань, як-от&str.