Визначення і створення екземпляра структури
Структури подібні до кортежів, про які ми говорили в підрозділі “Тип кортеж” Розділу 3, бо обидва складаються з кількох пов'язаних значень. Як і у кортежах, частини структур можуть бути різних типів. На відміну від кортежів, у структурі ви називаєте кожен елемент даних, щоб було зрозуміло, що ці значення означають. Завдяки цим іменам структури гнучкіші за кортежі: ви не мусите покладатися на порядок даних, щоб визначати чи отримувати доступ до значень екземпляра.
Для визначення структури, ми вводимо ключове слово struct
і називаємо всю структуру. Ім'я структури має описувати сенс групування цих елементів даних. Потім, у фігурних дужках, ми визначаємо імена і типи елементів даних, які звуться полями. Наприклад, Блок коду 5-1 показує структуру, що зберігає інформацію про обліковий запис користувача.
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() {}
Щоб скористатися структурою по визначенню, ми створюємо екземпляр цієї структури, визначаючи конкретні значення для кожного поля. Ми створюємо екземпляр, вказуючи ім'я структури, а потім додаємо фігурні дужки, що містять пари ключ: значення
, де ключі - це імена полів, а значення - дані, які ми хочемо зберігати в цих полях. Поля не обов'язково вказувати у тому ж порядку, в якому вони були проголошені в структурі. Іншими словами, визначення структури - це загальний шаблон типу, а екземпляри заповнюють цей шаблон конкретними даними, щоб створити значення цього типу. Наприклад, ми можемо проголосити конкретного користувача, як показано в Блоці коду 5-2.
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { let user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; }
Щоб отримати конкретне значення зі структури, використовують записом через точку. Якщо ми хочемо отримати адресу електронної пошти користувача, ми можемо написати user1.email
. Якщо екземпляр є мутабельним, ми можемо змінити значення за допомогою запису через точку і присвоюванням конкретному полю. Блок коду 5-3 показує, як змінити значення поля email
мутабельного екземпляра User
.
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn main() { let mut user1 = User { email: String::from("someone@example.com"), username: String::from("someusername123"), active: true, sign_in_count: 1, }; user1.email = String::from("anotheremail@example.com"); }
Зверніть увагу, що мутабельним має бути весь екземпляр; Rust не дозволяє позначати лише окремі поля як мутабельні. Як і з будь-яким виразом, ми можемо написати новий екземпляр останнім виразом у тілі функції, щоб неявно повернути цей новий екземпляр.
Блок коду 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 { email: email, username: username, active: true, sign_in_count: 1, } } fn main() { let user1 = build_user( String::from("someone@example.com"), String::from("someusername123"), ); }
Має сенс називати аргументи такої функції тими ж іменами, що й імена відповідних полів структури, але необхідність повторювати імена полів email
та username
утомлює. Якщо у структурі більше полів, повторення кожного імені дратує ще більше. На щастя, є зручне скорочення!
Скорочення ініціалізації полів
Because the parameter names and the struct field names are exactly the same in Listing 5-4, we can use the field init shorthand syntax to rewrite build_user
so that it behaves exactly the same but doesn’t have the repetition of email
and username
, as shown in Listing 5-5.
struct User { active: bool, username: String, email: String, sign_in_count: u64, } fn build_user(email: String, username: String) -> User { User { email, username, active: true, 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
.
Створення екземплярів з інших екземплярів за допомогою синтаксису оновлення структур
Часто буває корисним створити новий екземпляр структури, що бере більшу частину даних з екземпляра, що вже існує, проте деякі змінює. Ви можете зробити так за допомогою синтаксису оновлення структури.
Для початку, Блок коду 5-6 показує, як створити новий екземпляр User
, що зветься user2
, без синтаксису оновлення. Ми виставляємо нове значення поля email
, проте решта полів використовує значення зі структури user1
, створеної у Блоці коду 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, }; }
Синтаксис оновлення структури дає той самий результат із меншою кількістю коду, як показано у Блоці коду 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 }; }
Код у Роздруку 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
, тому застосовується поведінка, яку ми обговорювали в підрозділі "Дані в стеку: копіювання" .
Використання структур-кортежів без названих полів для створення нових типів
Rust також підтримує структури, які виглядають схожими на кортежі, що звуться структури-кортежі (tuple struct). Структури-кортежі надають значення структурі, бо мають назву, але не мають назв полів, тільки типи. Структури-кортежі корисні, коли ви хочете дати кортежу ім'я і зробити кортеж окремим типом, але називати кожне поле, як у звичайній структурі, буде надто багатослівним чи надмірним.
Щоб визначити структуру-кортеж, треба вказати ключове слово 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
. В іншому ж структури-кортежі поводяться як кортежі: ви можете деструктуризувати їх на окремі шматки, і ви можете використовувати .
з індексом, щоб отримати доступ до окремого значення.
Одинично-подібні структури без полів
Також можна визначати структури без жодних полів! Вони звуться одинично-подібні структури (unit-like struct), бо поводяться аналогічно до ()
, одничного типу, згаданого в підрозділі “Тип кортеж” . Одинично-подібні структури можуть бути корисними в ситуаціях, коли вам потрібно реалізувати трейт на якомусь типі, але у вас немає потреби зберігати якісь дані в цьому типі. Про трейти ми поговоримо в Розділі 10. Ось приклад проголошення та створення одиничної структури під назвою AlwaysEqual
:
struct AlwaysEqual; fn main() { let subject = AlwaysEqual; }
Щоб визначити AlwaysEqual
, ми використовуємо ключове слово struct
, назву структури та крапку з комою. Дужки не потрібні - ані звичайні, ані фігурні! Тоді ми можемо створити екземпляр з AlwaysEqual
у змінній subject
у схожий спосіб: використовуючи ім'я, яке ми визначили, без будь-яких - звичайних чи фігурних - дужок. Уявімо, що згодом ми реалізуємо поведінку для цього типу, так що кожен екземпляр AlwaysEqual
завжди дорівнює будь-якому екземпляру будь-якого іншого типу, скажімо, щоб мати завжди відомий результат для тестування. Нам не потрібні жодні дані для реалізації такої поведінки! У Розділі 10 ви побачите, як визначити трейти і реалізувати їх на будь-якому типі, включно з одинично-подібними структурами.
Володіння даними структури
У структурі
User
з Блоку коду 5-1 ми використовували типString
, що має володіння, а не стрічковий слайс&str
. Це свідомий вибір, бо ми хочемо, щоб екземпляри цієї структури володіли всіма своїми даними і щоб ці дані були коректними, поки структури в цілому коректна.Структура також може зберігати посилання на дані, якими володіє хтось інший, але це потребує використання часу життя, особливості Rust, що обговорюється у Розділі 10. Час життя гарантує, що дані, на які посилається структура, будуть коректними весь час існування структури. Наприклад, якщо ви спробуєте зберегти посилання у структурі без уточнення часу життя, ось так, то дістанете помилку:
Файл: src/main.rs
struct User { active: bool, username: &str, email: &str, sign_in_count: u64, } fn main() { let user1 = User { email: "someone@example.com", username: "someusername123", active: true, 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` due to 2 previous errors
In Chapter 10, we’ll discuss how to fix these errors so you can store references in structs, but for now, we’ll fix errors like these using owned types like
String
instead of references like&str
.