Определение и инициализация структур

RustRustBeginner
Практиковаться сейчас

This tutorial is from open-source community. Access the source code

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

Добро пожаловать в Определение и инициализация структур. Эта лабораторная работа является частью Rust Book. Вы можете практиковать свои навыки Rust в LabEx.

В этой лабораторной работе мы узнаем, как определять и инициализировать структуры в Rust, где структуры хранят несколько связанных значений и могут иметь именованные поля, что позволяет более гибко использовать и получать доступ к данным.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL rust(("Rust")) -.-> rust/DataTypesGroup(["Data Types"]) rust(("Rust")) -.-> rust/FunctionsandClosuresGroup(["Functions and Closures"]) rust(("Rust")) -.-> rust/DataStructuresandEnumsGroup(["Data Structures and Enums"]) rust(("Rust")) -.-> rust/BasicConceptsGroup(["Basic Concepts"]) rust/BasicConceptsGroup -.-> rust/variable_declarations("Variable Declarations") rust/BasicConceptsGroup -.-> rust/mutable_variables("Mutable Variables") rust/DataTypesGroup -.-> rust/integer_types("Integer Types") rust/DataTypesGroup -.-> rust/boolean_type("Boolean Type") rust/DataTypesGroup -.-> rust/string_type("String Type") rust/FunctionsandClosuresGroup -.-> rust/function_syntax("Function Syntax") rust/FunctionsandClosuresGroup -.-> rust/expressions_statements("Expressions and Statements") rust/DataStructuresandEnumsGroup -.-> rust/method_syntax("Method Syntax") subgraph Lab Skills rust/variable_declarations -.-> lab-100395{{"Определение и инициализация структур"}} rust/mutable_variables -.-> lab-100395{{"Определение и инициализация структур"}} rust/integer_types -.-> lab-100395{{"Определение и инициализация структур"}} rust/boolean_type -.-> lab-100395{{"Определение и инициализация структур"}} rust/string_type -.-> lab-100395{{"Определение и инициализация структур"}} rust/function_syntax -.-> lab-100395{{"Определение и инициализация структур"}} rust/expressions_statements -.-> lab-100395{{"Определение и инициализация структур"}} rust/method_syntax -.-> lab-100395{{"Определение и инициализация структур"}} end

Определение и инициализация структур

Структуры похожи на кортежи, о которых говорилось в разделе "Тип кортежа", в том смысле, что и те, и другие хранят несколько связанных значений. Как и кортежи, элементы структуры могут быть разных типов. В отличие от кортежей, в структуре вы будете именовать каждый элемент данных, чтобы было ясно, что значат эти значения. Добавление этих имен означает, что структуры более гибкие, чем кортежи: вам не нужно полагаться на порядок данных для указания или доступа к значениям экземпляра.

Для определения структуры мы вводим ключевое слово struct и именуем всю структуру. Имя структуры должно описывать значение элементов данных, группируемых вместе. Затем, внутри фигурных скобок, мы определяем имена и типы элементов данных, которые мы называем полями. Например, в Листинге 5-1 показана структура, которая хранит информацию о пользовательском аккаунте.

Filename: src/main.rs

struct User {
    active: bool,
    username: String,
    email: String,
    sign_in_count: u64,
}

Листинг 5-1: Определение структуры User

После определения структуры, чтобы использовать ее, мы создаем экземпляр этой структуры, указав конкретные значения для каждого из полей. Мы создаем экземпляр, указывая имя структуры, а затем добавляем фигурные скобки, содержащие пары ключ: значение, где ключи - это имена полей, а значения - данные, которые мы хотим хранить в этих полях. Мы не обязаны указывать поля в том же порядке, в котором они были объявлены в структуре. Другими словами, определение структуры похоже на общий шаблон для типа, а экземпляры заполняют этот шаблон конкретными данными, чтобы создать значения этого типа. Например, мы можем объявить конкретного пользователя, как показано в Листинге 5-2.

Filename: src/main.rs

fn main() {
    let user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("[email protected]"),
        sign_in_count: 1,
    };
}

Листинг 5-2: Создание экземпляра структуры User

Для получения конкретного значения из структуры мы используем точку. Например, чтобы получить адрес электронной почты этого пользователя, мы используем user1.email. Если экземпляр изменяемый, мы можем изменить значение, используя точку и присваивание в конкретное поле. Листинг 5-3 показывает, как изменить значение в поле email изменяемого экземпляра User.

Filename: src/main.rs

fn main() {
    let mut user1 = User {
        active: true,
        username: String::from("someusername123"),
        email: String::from("[email protected]"),
        sign_in_count: 1,
    };

    user1.email = String::from("[email protected]");
}

Листинг 5-3: Изменение значения в поле email экземпляра User

Обратите внимание, что весь экземпляр должен быть изменяемым; Rust не позволяет нам помечать только некоторые поля как изменяемые. Как и с любым выражением, мы можем создать новый экземпляр структуры в качестве последнего выражения в теле функции, чтобы неявно вернуть этот новый экземпляр.

Листинг 5-4 показывает функцию build_user, которая возвращает экземпляр User с заданным адресом электронной почты и именем пользователя. Поле active получает значение true, а поле sign_in_count получает значение 1.

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username: username,
        email: email,
        sign_in_count: 1,
    }
}

Листинг 5-4: Функция build_user, которая принимает адрес электронной почты и имя пользователя и возвращает экземпляр User

Смысл в том, чтобы именовать параметры функции тем же именем, что и поля структуры, но приходится повторять имена полей email и username и переменные, что немного утомительно. Если структура имела больше полей, повторение каждого имени было бы еще более раздражающим. К счастью, есть удобный сокращение!

Использование сокращенной записи инициализации полей

Поскольку имена параметров и имена полей структуры совпадают в Листинге 5-4, мы можем использовать синтаксис сокращенной записи инициализации полей, чтобы переписать build_user, чтобы она работала точно так же, но не повторяла username и email, как показано в Листинге 5-5.

fn build_user(email: String, username: String) -> User {
    User {
        active: true,
        username,
        email,
        sign_in_count: 1,
    }
}

Листинг 5-5: Функция build_user, которая использует сокращенную запись инициализации полей, потому что параметры username и email имеют то же имя, что и поля структуры

Здесь мы создаем новый экземпляр структуры User, которая имеет поле с именем email. Мы хотим установить значение поля email равным значению параметра email функции build_user. Поскольку поле email и параметр email имеют одно и то же имя, нам нужно только написать email, а не email: email.

Создание экземпляров из других экземпляров с использованием синтаксиса обновления структуры

Часто полезно создать новый экземпляр структуры, который включает в себя большинство значений из другого экземпляра, но изменяет некоторые из них. Это можно сделать с использованием синтаксиса обновления структуры.

Во - первых, в Листинге 5-6 показано, как создать новый экземпляр User в user2 обычным способом, без синтаксиса обновления. Мы устанавливаем новое значение для email, а в остальном используем те же значения, что и в user1, который мы создали в Листинге 5-2.

Filename: src/main.rs

fn main() {
    --snip--

    let user2 = User {
        active: user1.active,
        username: user1.username,
        email: String::from("[email protected]"),
        sign_in_count: user1.sign_in_count,
    };
}

Листинг 5-6: Создание нового экземпляра User с использованием одного из значений из user1

С использованием синтаксиса обновления структуры мы можем достичь того же эффекта с меньшим количеством кода, как показано в Листинге 5-7. Синтаксис .. указывает, что оставшиеся не явно заданные поля должны иметь те же значения, что и поля в заданном экземпляре.

Filename: src/main.rs

fn main() {
    --snip--


    let user2 = User {
        email: String::from("[email protected]"),
      ..user1
    };
}

Листинг 5-7: Использование синтаксиса обновления структуры для установки нового значения для email в экземпляре User, но использования остальных значений из user1

Код в Листинге 5-7 также создает экземпляр в user2, который имеет другое значение для email, но имеет те же значения для полей username, active и sign_in_count из user1. ..user1 должно быть в конце, чтобы указать, что любые оставшиеся поля должны получать свои значения из соответствующих полей в user1, но мы можем выбрать указать значения для любого количества полей в любом порядке, независимо от порядка полей в определении структуры.

Обратите внимание, что синтаксис обновления структуры использует = подобно оператору присваивания; это потому, что он перемещает данные, как мы видели в разделе "Переменные и данные, взаимодействующие с Move". В этом примере мы больше не можем использовать user1 после создания user2, потому что String в поле username из user1 был перемещен в user2. Если бы мы дали user2 новые значения String для обоих email и username, и таким образом использовали только значения active и sign_in_count из user1, то user1 по-прежнему был бы валидным после создания user2. active и sign_in_count - это типы, которые реализуют трейт Copy, поэтому поведение, о котором мы говорили в разделе "Только стековые данные: Copy", применилось бы.

Использование структур - кортежей без именованных полей для создания различных типов

Rust также поддерживает структуры, которые похожи на кортежи, называемые структуры - кортежи. Структуры - кортежи имеют дополнительное значение, которое предоставляет имя структуры, но не имеют имен, связанных с их полями; вместо этого у них есть только типы полей. Структуры - кортежи полезны, когда вы хотите дать целому кортежу имя и сделать кортеж другим типом по сравнению с другими кортежами, и когда именование каждого поля, как в обычной структуре, будет избыточным или излишним.

Для определения структуры - кортежа начните с ключевого слова struct и имени структуры, за которым следуют типы в кортеже. Например, здесь мы определяем и используем две структуры - кортежа с именами Color и Point:

Filename: src/main.rs

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. В остальном экземпляры структур - кортежей похожи на кортежи в том смысле, что вы можете разобрать их на отдельные части, и вы можете использовать . за которым следует индекс, чтобы получить доступ к отдельному значению.

Структуры, подобные единице, без полей

Вы также можете определить структуры, которые не имеют никаких полей! Они называются структуры, подобные единице, потому что они ведут себя подобно (), типу единицы, о котором мы говорили в разделе "Тип кортежа". Структуры, подобные единице, могут быть полезны, когда вам нужно реализовать трейт для какого - то типа, но у вас нет никаких данных, которые вы бы хотели хранить в самом типе. Мы обсудим трейты в главе 10. Вот пример объявления и инициализации структуры - единицы с именем AlwaysEqual:

Filename: src/main.rs

struct AlwaysEqual;

fn main() {
    let subject = AlwaysEqual;
}

Для определения AlwaysEqual мы используем ключевое слово struct, имя, которое мы хотим, и затем точку с запятой. Не нужно фигурных скобок или круглых скобок! Затем мы можем получить экземпляр AlwaysEqual в переменной subject аналогичным образом: используя имя, которое мы определили, без каких - то фигурных скобок или круглых скобок. Представьте, что позже мы реализуем поведение для этого типа так, чтобы каждый экземпляр AlwaysEqual всегда был равен любому другому экземпляру любого другого типа, возможно, для получения известного результата для целей тестирования. Для реализации этого поведения нам не понадобятся никакие данные! В главе 10 вы увидите, как определить трейты и реализовать их для любого типа, включая структуры, подобные единице.

Владеяние данными структуры

В определении структуры User в Листинге 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: "[email protected]",
        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,
  |

В главе 10 мы обсудим, как исправить эти ошибки, чтобы вы могли хранить ссылки в структурах, но на данный момент мы будем исправлять такие ошибки, используя типы, имеющие владение, такие как String, вместо ссылок, таких как &str.

Резюме

Поздравляем! Вы завершили лабораторную работу по определению и инициализации структур. Вы можете выполнить больше лабораторных работ в LabEx, чтобы улучшить свои навыки.