Главная страница
Навигация по странице:

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

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

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

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

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

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

  • Создание экземпляра структуры из экземпляра другой структуры с

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

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

  • Кортежные структуры: структуры без именованных полей для создания разных типов

  • Единично-подобные структуры: структуры без полей

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

  • Пример использования структур

  • Листинг 5-8: вычисление площади прямоугольника, заданного отдельными переменными ширины и высоты

  • Рефакторинг при помощи кортежей

  • Рефакторинг при помощи структур: добавим больше смысла

  • Листинг 5-10: определение структуры

  • Добавление полезной функциональности при помощи выводимых

  • Язык программирования Rust


    Скачать 7.02 Mb.
    НазваниеЯзык программирования Rust
    Дата12.04.2023
    Размер7.02 Mb.
    Формат файлаpdf
    Имя файлаThe Rust Programming Language_ru.pdf
    ТипУчебник
    #1056301
    страница10 из 62
    1   ...   6   7   8   9   10   11   12   13   ...   62

    Определение и инициализация структур
    Структуры похожи на кортежи, рассмотренные в разделе "Кортежи"
    , так как оба хранят несколько связанных значений. Как и кортежи, части структур могут быть разных типов.
    В отличие от кортежей, в структуре необходимо именовать каждую часть данных для понимания смысла значений. Добавление этих имён обеспечивает большую гибкость структур по сравнению с кортежами: не нужно полагаться на порядок данных для указания значений экземпляра или доступа к ним.
    Для определения структуры указывается ключевое слово struct и её название.
    Название должно описывать значение частей данных, сгруппированных вместе. Далее, в фигурных скобках для каждой новой части данных поочерёдно определяются имя части данных и её тип. Каждая пара имя: тип называется полем. Листинг 5-1 описывает структуру для хранения информации об учётной записи пользователя:
    Листинг 5-1: Определение структуры
    User
    После определения структуры можно создавать её экземпляр, назначая определённое значение каждому полю с соответствующим типом данных. Чтобы создать экземпляр, мы указываем имя структуры, затем добавляем фигурные скобки и включаем в них пары ключ: значение
    (key: value), где ключами являются имена полей, а значениями являются данные, которые мы хотим сохранить в полях. Нет необходимости чётко следовать порядку объявления полей в описании структуры (но всё-таки желательно для удобства чтения). Другими словами, объявление структуры - это как шаблон нашего типа, в то время как экземпляр структуры использует этот шаблон, заполняя его определёнными данными, для создания значений нашего типа. Например, можно объявить пользователя как в листинге 5-2:
    Листинг 5-2: Создание экземпляра структуры
    User
    Чтобы получить конкретное значение из структуры, мы используем запись через точку.
    Например, чтобы получить доступ к адресу электронной почты этого пользователя, мы 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
    Листинг 5-3: Изменение значения в поле
    email
    экземпляра
    User
    Заметим, что весь экземпляр структуры должен быть изменяемым; Rust не позволяет помечать изменяемыми отдельные поля. Как и для любого другого выражения, мы можем использовать выражение создания структуры в качестве последнего выражения тела функции для неявного возврата нового экземпляра.
    На листинге 5-4 функция build_user возвращает экземпляр
    User с указанным адресом и именем. Поле active получает значение true
    , а поле sign_in_count получает значение
    1
    Листинг 5-4: Функция
    build_user
    , которая принимает email и имя пользователя и возвращает экземпляр
    User
    Имеет смысл называть параметры функции теми же именами, что и поля структуры, но необходимость повторять email и username для названий полей и переменных несколько утомительна. Если структура имеет много полей, повторение каждого имени станет ещё более раздражающим. К счастью, есть удобное сокращение!
    Использование сокращённой инициализации поля
    Так как имена входных параметров функции и полей структуры являются полностью идентичными в листинге 5-4, возможно использовать синтаксис сокращённой
    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"
    );
    } fn build_user
    (email:
    String
    , username:
    String
    ) -> User {
    User { email: email, username: username, active: true
    , sign_in_count:
    1
    ,
    }
    }

    инициализации поля, чтобы переписать build_user так, чтобы он работал точно также,
    но не содержал повторений для email и username
    , как в листинге 5-5.
    Листинг 5-5: Функция
    build_user
    , использующая сокращённую инициализацию поля, когда параметры
    email
    и
    username
    имеют те же имена, что и поля struct
    Здесь происходит создание нового экземпляра структуры
    User
    , которая имеет поле с именем email
    . Мы хотим установить поле структуры email значением входного параметра email функции build_user
    . Так как поле email и входной параметр функции email имеют одинаковое название, можно писать просто email вместо кода email: email
    Создание экземпляра структуры из экземпляра другой структуры с
    помощью синтаксиса обновления структуры
    Часто бывает полезно создать новый экземпляр структуры, который включает большинство значений из другого экземпляра, но некоторые из них изменяет. Это можно сделать с помощью синтаксиса обновления структуры.
    Сначала в листинге 5-6 показано, как обычно создаётся новый экземпляр
    User в user2
    без синтаксиса обновления. Мы задаём новое значение для email
    , но в остальном используем те же значения из user1
    , которые были заданы в листинге 5-2.
    Листинг 5-6: Создание нового экземпляра
    User
    с использованием некоторых значений из экземпляра
    user1
    Используя синтаксис обновления структуры, можно получить тот же эффект, используя меньше кода как показано в листинге 5-7. Синтаксис указывает, что оставшиеся поля fn build_user
    (email:
    String
    , username:
    String
    ) -> User {
    User { email, username, active: true
    , sign_in_count:
    1
    ,
    }
    } fn main
    () {
    // --snip-- let user2 = User { active: user1.active, username: user1.username, email:
    String
    ::from(
    "another@example.com"
    ), sign_in_count: user1.sign_in_count,
    };
    }
    устанавливаются неявно и должны иметь значения из указанного экземпляра.
    Листинг 5-7: Использование синтаксиса обновления структуры для установки нового значения
    email
    для
    экземпляра
    User
    , но использование остальных значений из экземпляра
    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 также поддерживает структуры, похожие на кортежи, которые называются
    кортежные структуры. Кортежные структуры обладают дополнительным смыслом,
    который даёт имя структуры, но при этом не имеют имён, связанных с их полями. Скорее,
    они просто хранят типы полей. Кортежные структуры полезны, когда вы хотите дать имя всему кортежу и сделать кортеж отличным от других кортежей, и когда именование каждого поля, как в обычной структуре, было бы многословным или избыточным.
    Чтобы определить кортежную структуру, начните с ключевого слова struct и имени структуры, за которым следуют типы в кортеже. Например, здесь мы определяем и используем две кортежные структуры с именами
    Color и
    Point
    :
    fn main
    () {
    // --snip-- let user2 = User { email:
    String
    ::from(
    "another@example.com"
    ),
    ..user1
    };
    }

    Обратите внимание, что значения black и origin
    — это разные типы, потому что они являются экземплярами разных кортежных структур. Каждая определяемая вами структура имеет собственный тип, даже если поля внутри структуры могут иметь одинаковые типы. Например, функция, принимающая параметр типа
    Color
    , не может принимать
    Point в качестве аргумента, даже если оба типа состоят из трёх значений i32
    . В остальном экземпляры кортежных структур похожи на кортежи в том смысле, что вы можете деструктурировать их на отдельные части и использовать
    , за которой следует индекс для доступа к отдельному значению.
    Единично-подобные структуры: структуры без полей
    Также можно определять структуры, не имеющие полей! Они называются единично-
    подобными структурами, поскольку ведут себя аналогично
    ()
    , единичному типу, о котором мы говорили в разделе "Кортежи"
    . Единично-подобные структуры могут быть полезны, когда требуется реализовать типаж для некоторого типа, но у вас нет данных,
    которые нужно хранить в самом типе. Мы обсудим типажи в главе 10. Вот пример объявления и создание экземпляра единичной структуры с именем
    AlwaysEqual
    :
    Чтобы определить
    AlwaysEqual
    , мы используем ключевое слово struct
    , желаемое имя,
    а затем точку с запятой. Нет необходимости в фигурных или круглых скобках! Затем мы можем получить экземпляр
    AlwaysEqual в переменной subject аналогичным образом:
    используя имя, которое мы определили, без фигурных и круглых скобок. Представим, что в дальнейшем мы реализуем поведение для этого типа таким образом, что каждый экземпляр
    AlwaysEqual всегда будет равен каждому экземпляру любого другого типа,
    возможно, с целью получения ожидаемого результата для тестирования. Для реализации такого поведения нам не нужны никакие данные! В главе 10 вы увидите, как определять черты и реализовывать их для любого типа, включая единично-подобные структуры.
    Владение данными структуры
    struct
    Color
    (
    i32
    , i32
    , i32
    ); struct
    Point
    (
    i32
    , i32
    , i32
    ); fn main
    () { let black = Color(
    0
    ,
    0
    ,
    0
    ); let origin = Point(
    0
    ,
    0
    ,
    0
    );
    } struct
    AlwaysEqual
    ; fn main
    () { let subject = AlwaysEqual;
    }

    В определении структуры
    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
    ,
    };
    }

    В главе 10 мы обсудим, как исправить эти ошибки, чтобы иметь возможность хранить ссылки в структурах, а пока мы исправим подобные ошибки, используя владеющие типы вроде
    String вместо ссылок
    &str
    $
    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

    Пример использования структур
    Чтобы понять, когда нам может понадобиться использование структур, давайте напишем программу, которая вычисляет площадь прямоугольника. Мы начнём с использования одиночных переменных, а затем будем улучшать программу до использования структур.
    Давайте создадим новый проект программы при помощи Cargo и назовём его rectangles.
    Наша программа будет получать на вход длину и ширину прямоугольника в пикселях и затем рассчитывать площадь прямоугольника. Листинг 5-8 показывает один из коротких вариантов кода, который позволит нам сделать именно то, что надо, в файле проекта
    src/main.rs.
    Файл: src/main.rs
    Листинг 5-8: вычисление площади прямоугольника, заданного отдельными переменными ширины и
    высоты
    Теперь запустим программу, используя cargo run
    :
    Этот код успешно вычисляет площадь прямоугольника, вызывая функцию area с
    каждым измерением, но мы можем улучшить его ясность и читабельность.
    Проблема данного метода очевидна из сигнатуры area
    :
    Функция area должна вычислять площадь одного прямоугольника, но функция, которую мы написали, имеет два параметра, и нигде в нашей программе не ясно, что эти параметры взаимосвязаны. Было бы более читабельным и управляемым сгруппировать fn main
    () { let width1 =
    30
    ; let height1 =
    50
    ; println!
    (
    "The area of the rectangle is {} square pixels."
    , area(width1, height1)
    );
    } fn area
    (width: u32
    , height: u32
    ) -> u32
    { width * height
    }
    $
    cargo run
    Compiling rectangles v0.1.0 (file:///projects/rectangles)
    Finished dev [unoptimized + debuginfo] target(s) in 0.42s
    Running `target/debug/rectangles`
    The area of the rectangle is 1500 square pixels. fn area
    (width: u32
    , height: u32
    ) -> u32
    {
    ширину и высоту вместе. В разделе
    «Кортежи»
    главы 3 мы уже обсуждали один из способов сделать это — использовать кортежи.
    Рефакторинг при помощи кортежей
    Листинг 5-9 — это другая версия программы, использующая кортежи.
    Файл: src/main.rs
    Листинг 5-9: определение ширины и высоты прямоугольника с помощью кортежа
    С одной стороны, эта программа лучше. Кортежи позволяют добавить немного структуры, и теперь мы передаём только один аргумент. Но с другой стороны, эта версия менее понятна: кортежи не называют свои элементы, поэтому нам приходится индексировать части кортежа, что делает наше вычисление менее очевидным.
    Если мы перепутаем местами ширину с высотой при расчёте площади, то это не имеет значения. Но если мы хотим нарисовать прямоугольник на экране, то это уже будет важно! Мы должны помнить, что ширина width находится в кортеже с индексом
    0
    , а высота height
    — с индексом
    1
    . Если кто-то другой поработал бы с кодом, ему бы пришлось разобраться в этом и также помнить про порядок. Легко забыть и перепутать эти значения — и это вызовет ошибки, потому что данный код не передаёт наши намерения.
    Рефакторинг при помощи структур: добавим больше смысла
    Мы используем структуры, чтобы добавить смысл данным при помощи назначения им осмысленных имён . Мы можем переделать используемый кортеж в структуру с единым именем для сущности и частными названиями её частей, как показано в листинге 5-10.
    Файл: src/main.rs fn main
    () { let rect1 = (
    30
    ,
    50
    ); println!
    (
    "The area of the rectangle is {} square pixels."
    , area(rect1)
    );
    } fn area
    (dimensions: (
    u32
    , u32
    )) -> u32
    { dimensions.
    0
    * dimensions.
    1
    }

    Листинг 5-10: определение структуры
    Rectangle
    Здесь мы определили структуру и дали ей имя
    Rectangle
    . Внутри фигурных скобок определили поля как width и height
    , оба — типа u32
    . Затем в main создали конкретный экземпляр
    Rectangle с шириной в 30 и высотой в 50 единиц.
    Наша функция area теперь определена с одним параметром, названным rectangle
    , чей тип является неизменяемым заимствованием структуры
    Rectangle
    . Как упоминалось в главе 4, необходимо заимствовать структуру, а не передавать её во владение. Таким образом функция main сохраняет rect1
    в собственности и может использовать её
    дальше. По этой причине мы и используем
    &
    в сигнатуре и в месте вызова функции.
    Функция area получает доступ к полям width и height экземпляра
    Rectangle
    (обратите внимание, что доступ к полям заимствованного экземпляра структуры не приводит к перемещению значений полей, поэтому вы часто видите заимствования структур). Наша сигнатура функции для area теперь говорит именно то, что мы имеем в виду: вычислить площадь
    Rectangle
    , используя его поля width и height
    . Это означает, что ширина и высота связаны друг с другом, и даёт описательные имена значениям, а не использует значения индекса кортежа
    0
    и
    1
    . Это торжество ясности.
    Добавление полезной функциональности при помощи выводимых
    1   ...   6   7   8   9   10   11   12   13   ...   62


    написать администратору сайта