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

  • Пакеты

  • Определение модулей для контроля видимости и закрытости

  • Скрытие или общедоступность

  • Группировка связанного кода в модулях

  • Листинг 7-1: Модуль front_of_house , содержащий другие модули, которые в свою очередь содержат функции

  • Листинг 7-2: Дерево модулей для для структуры модулей приведённой в коде в листинге 7-1

  • Пути для ссылки на элемент в дереве модулей

  • Листинг 7-3. Вызов функции add_to_waitlist с использованием абсолютного и относительного пути

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


    Скачать 7.02 Mb.
    НазваниеЯзык программирования Rust
    Дата12.04.2023
    Размер7.02 Mb.
    Формат файлаpdf
    Имя файлаThe Rust Programming Language_ru.pdf
    ТипУчебник
    #1056301
    страница14 из 62
    1   ...   10   11   12   13   14   15   16   17   ...   62
    Управление растущими проектами с
    помощью пакетов, крейтов и модулей
    По мере роста кодовой базы ваших программ, организация проекта будет иметь большое значение, ведь отслеживание всей программы в голове будет становиться всё
    более сложным. Группируя связанные функции и разделяя код по основным функциональностям (фичам, feature), вы делаете более прозрачным понимание о том, где искать код реализующий определённую функцию и где стоит вносить изменения для того чтобы изменить её поведение.
    Программы, которые мы писали до сих пор, были в одном файле одного модуля. По мере роста проекта, мы можем организовывать код иначе, разделив его на несколько модулей и несколько файлов. Пакет может содержать несколько бинарных крейтов и опционально один крейт библиотеки. Пакет может включать в себя много бинарных крейтов и опционально один библиотечный крейт. По мере роста пакета вы можете извлекать части программы в отдельные крейты, которые затем станут внешними зависимостями для основного кода нашей программы. Эта глава охватывает все эти техники. В свою очередь для очень крупных проектов, состоящих из набора взаимосвязанных пакетов развивающихся вместе, Cargo предоставляет рабочие пространства, workspaces, их мы рассмотрим за пределами данной главы, в разделе "Рабочие пространства Cargo"
    Главы 14.
    Мы также обсудим инкапсуляцию деталей, которая позволяет использовать код снова на более высоком уровне: единожды реализовав какую-то операцию, другой код может вызывать этот код через публичный интерфейс, не зная как работает реализация. То, как вы пишете код, определяет какие части общедоступны для использования другим кодом и какие части являются закрытыми деталями реализации для которых вы оставляете право на изменения только за собой. Это ещё один способ ограничить количество деталей, которые вы должны держать в голове.
    Связанное понятие - это область видимости: вложенный контекст в котором написан код имеющий набор имён, которые определены «в текущей области видимости». При чтении, письме и компиляции кода, программистам и компиляторам необходимо знать,
    относится ли конкретное имя в определённом месте к переменной, к функции, к структуре, к перечислению, к модулю, к константе или другому элементу и что означает этот элемент. Можно создавать области видимости и изменять какие имена входят или выходят за их рамки. Нельзя иметь два элемента с тем же именем в одной области; есть доступные инструменты для разрешения конфликтов имён.
    Rust имеет ряд функций, которые позволяют управлять организацией кода, в том числе управлять тем какие детали открыты, какие детали являются частными, какие имена есть в каждой области вашей программы. Эти функции иногда вместе именуемые модульной
    системой включают в себя:

    Пакеты: Функционал Cargo позволяющий собирать, тестировать и делиться крейтами
    Крейты: Дерево модулей, которое создаёт библиотечный или исполняемый файл
    Модули и use: Позволяют вместе контролировать организацию, область видимости и скрытие путей
    Пути: способ именования элемента, такого как структура, функция или модуль
    В этой главе мы рассмотрим все эти функции, обсудим как они взаимодействуют и объясним, как использовать их для управления областью видимости. К концу у вас должно появиться солидное понимание модульной системы и умение работать с областями видимости на уровне профессионала!

    Пакеты и крейты
    Первые части модульной системы, которые мы рассмотрим — это пакеты и крейты.
    Крейт — это наименьший объем кода, который компилятор Rust рассматривает за раз.
    Даже если вы запустите rustc вместо cargo и передадите один файл с исходным кодом
    (как мы уже делали в разделе «Написание и запуск программы на Rust» Главы 1),
    компилятор считает этот файл крейтом. Крейты могут содержать модули, и модули могут быть определены в других файлах, которые компилируются вместе с крейтом, как мы увидим в следующих разделах.
    Крейт может быть одним из двух видов: бинарный крейт или библиотечный крейт.
    Бинарные крейты — это программы, которые вы можете скомпилировать в исполняемые файлы, которые вы можете запускать, например программу командной строки или сервер. У каждого бинарного крейта должна быть функция с именем main
    , которая определяет, что происходит при запуске исполняемого файла. Все крейты, которые мы создали до сих пор, были бинарными крейтами.
    Библиотечные крейты не имеют функции main и не компилируются в исполняемый файл. Вместо этого они определяют функциональность, предназначенную для совместного использования другими проектами. Например, крейт rand
    , который мы использовали в
    Главе 2
    обеспечивает функциональность, которая генерирует случайные числа. В большинстве случаев, когда Rustaceans говорят «крейт», они имеют в виду библиотечный крейт, и они используют «крейт» взаимозаменяемо с общей концепцией программирования «библиотека».
    Корневой модуль крейта — это исходный файл, из которого компилятор Rust начинает собирать корневой модуль вашего крейта (мы подробно объясним модули в разделе
    «Определение модулей для контроля видимости и закрытости»
    ).
    Пакет — это набор из одного или нескольких крейтов, предоставляющий набор функциональности. Пакет содержит файл Cargo.toml, в котором описывается, как собирать эти крейты. На самом деле Cargo — это пакет, содержащий бинарный крейт для инструмента командной строки, который вы использовали для создания своего кода.
    Пакет Cargo также содержит библиотечный крейт, от которого зависит бинарный крейт.
    Другие проекты тоже могут зависеть от библиотечного крейта Cargo, чтобы использовать ту же логику, что и инструмент командной строки Cargo.
    Пакет может содержать сколько угодно бинарных крейтов, но не более одного библиотечного крейта. Пакет должен содержать хотя бы один крейт, библиотечный или бинарный.
    Давайте пройдёмся по тому, что происходит, когда мы создаём пакет. Сначала введём команду cargo new
    :

    После того, как мы запустили cargo new
    , мы используем ls
    , чтобы увидеть, что создал
    Cargo. В каталоге проекта есть файл Cargo.toml, дающий нам пакет. Также есть каталог src,
    содержащий main.rs. Откройте Cargo.toml в текстовом редакторе и обратите внимание,
    что в нём нет упоминаний о src/main.rs. Cargo следует соглашению о том, что src/main.rs
    — это корневой модуль бинарного крейта с тем же именем, что и у пакета. Точно так же
    Cargo знает, что если каталог пакета содержит src/lib.rs, пакет содержит библиотечный крейт с тем же именем, что и пакет, а src/lib.rs является корневым модулем этого крейта.
    Cargo передаёт файлы корневого модуля крейта в rustc для сборки библиотечного или бинарного крейта.
    Здесь у нас есть пакет, который содержит только src/main.rs, что означает, что он содержит только бинарный крейт с именем my-project
    . Если пакет содержит src/main.rs и
    src/lib.rs, он имеет два крейта: бинарный и библиотечный, оба с тем же именем, что и пакет. Пакет может иметь несколько бинарных крейтов, помещая их файлы в каталог
    src/bin: каждый файл будет отдельным бинарным крейтом.
    $
    cargo new my-project
    Created binary (application) `my-project` package
    $
    ls my-project
    Cargo.toml src
    $
    ls my-project/src main.rs

    Определение модулей для контроля видимости и
    закрытости
    В этом разделе мы поговорим о модулях и других частях системы модулей, а именно:
    путях (paths), которые позволяют именовать элементы; ключевом слове use
    , которое приносит путь в область видимости; ключевом слове pub
    , которое делает элементы общедоступными. Мы также обсудим ключевое слово as
    , внешние пакеты и оператор glob. А пока давайте сосредоточимся на модулях!
    Во-первых, мы начнём со списка правил, чтобы вам было легче ориентироваться при организации кода в будущем. Затем мы подробно объясним каждое из правил.
    Шпаргалка по модулям
    Здесь мы даём краткий обзор того, как модули, пути, ключевое слово use и ключевое слово pub работают в компиляторе и как большинство разработчиков организуют свой код. В этой главе мы рассмотрим примеры каждого из этих правил, и это удобный момент чтобы напомнить о том, как работают модули.
    Начнём с корня крейта: при компиляции компилятор сначала ищет корневой модуль крейта (обычно это src/lib.rs для библиотечного крейта или src/main.rs для бинарного крейта) для компиляции кода.
    Объявление модулей: В файле корневого модуля крейта вы можете объявить новые модули; скажем, вы объявляете модуль “garden” с помощью mod garden;
    Компилятор будет искать код модуля в следующих местах:
    в этом же файле, между фигурных скобок, которые заменяют точку с запятой после mod garden в файле src/garden.rs
    в файле src/garden/mod.rs
    Объявление подмодулей: В любом файле, кроме корневого модуля крейта, вы можете объявить подмодули. К примеру, вы можете объявить mod vegetables;
    в
    src/garden.rs. Компилятор будет искать код подмодуля в каталоге с именем родительского модуля в следующих местах:
    в этом же файле, сразу после mod vegetables
    , между фигурных скобок, которые заменяют точку с запятой в файле src/garden/vegetables.rs
    в файле src/garden/vegetables/mod.rs
    Пути к коду в модулях: После того, как модуль станет частью вашего крейта и если допускают правила приватности, вы можете ссылаться на код в этом модуле из любого места вашего крейта, используя путь к коду. Например, тип
    Asparagus
    , в подмодуле vegetables модуля garden, будет найден по пути crate::garden::vegetables::Asparagus

    Скрытие или общедоступность: Код в модуле по умолчанию скрыт от родительского модуля. Чтобы сделать модуль общедоступным, объявите его как pub mod вместо mod
    . Чтобы сделать элементы общедоступного модуля тоже общедоступными, используйте pub перед их объявлением.
    Ключевое слово
    use
    : Внутри области видимости использование ключевого слова use создаёт псевдонимы для элементов, чтобы уменьшить повторение длинных путей. В любой области видимости, в которой может обращаться к crate::garden::vegetables::Asparagus
    , вы можете создать псевдоним use crate::garden::vegetables::Asparagus;
    и после этого вам нужно просто писать
    Asparagus
    , чтобы использовать этот тип в этой области видимости.
    Мы создали бинарный крейт backyard
    , который иллюстрирует эти правила. Директория крейта, также названная как backyard
    , содержит следующие файлы и директории:
    Файл корневого модуля крейта в нашем случае src/main.rs, и его содержимое:
    Файл: src/main.rs
    Строка pub mod garden;
    говорит компилятору о подключении кода, найденном в
    src/garden.rs:
    Файл: src/garden.rs
    А здесь pub mod vegetables;
    указывает на подключаемый код в src/garden/vegetables.rs.
    Этот код:
    backyard
    ├── Cargo.lock
    ├── Cargo.toml
    └── src
    ├── garden
    │ └── vegetables.rs
    ├── garden.rs
    └── main.rs use crate::garden::vegetables::Asparagus; pub mod garden; fn main
    () { let plant = Asparagus {}; println!
    (
    "I'm growing {:?}!"
    , plant);
    } pub mod vegetables;
    #[derive(Debug)]
    pub struct
    Asparagus
    {}

    Теперь давайте рассмотрим детали этих правил и продемонстрируем их в действии!
    Группировка связанного кода в модулях
    Модули позволяют упорядочивать код внутри крейта для удобочитаемости и лёгкого повторного использования. Модули также позволяют нам управлять приватностью
    элементов, поскольку код внутри модуля по умолчанию является закрытым. Частные элементы — это внутренние детали реализации, недоступные для внешнего использования. Мы можем сделать модули и элементы внутри них общедоступными, что позволит внешнему коду использовать их и зависеть от них.
    В качестве примера, давайте напишем библиотечный крейт предоставляющий функциональность ресторана. Мы определим сигнатуры функций, но оставим их тела пустыми, чтобы сосредоточиться на организации кода, вместо реализации кода для ресторана.
    В ресторанной индустрии некоторые части ресторана называются фронтом дома, а другие задней частью дома. Фронт дома это там где находятся клиенты; здесь размещаются места клиентов, официанты принимают заказы и оплаты, а бармены делают напитки. Задняя часть дома это где шеф-повара и повара работают на кухне,
    работают посудомоечные машины, а менеджеры занимаются административной деятельностью.
    Чтобы структурировать крейт аналогично тому, как работает настоящий ресторан,
    можно организовать размещение функций во вложенных модулях. Создадим новую библиотеку (библиотечный крейт) с именем restaurant выполнив команду cargo new restaurant --lib
    ; затем вставим код из листинга 7-11 в src/lib.rs для определения некоторых модулей и сигнатур функций. Это секция фронта дома:
    Файл: src/lib.rs mod front_of_house { mod hosting { fn add_to_waitlist
    () {} fn seat_at_table
    () {}
    } mod serving { fn take_order
    () {} fn serve_order
    () {} fn take_payment
    () {}
    }
    }

    Листинг 7-1: Модуль
    front_of_house
    , содержащий другие модули, которые в свою очередь содержат
    функции
    Мы определяем модуль, начиная с ключевого слова mod
    , затем определяем название модуля (в данном случае front_of_house
    ) и размещаем фигурные скобки вокруг тела модуля. Внутри модулей, можно иметь другие модули, как в случае с модулями hosting и
    serving
    . Модули также могут содержать определения для других элементов, таких как структуры, перечисления, константы, типажи или — как в листинге 7-1 — функции.
    Используя модули, мы можем сгруппировать связанные определения вместе и сказать почему они являются связанными. Программистам будет легче найти необходимую функциональность в сгруппированном коде, вместо того чтобы искать её в одном общем списке. Программисты, добавляющие новые функции в этот код, будут знать, где разместить код для поддержания порядка в программе.
    Как мы упоминали ранее, файлы src/main.rs и src/lib.rs называются корневыми модулями
    крейта. Причина такого именования в том, что содержимое любого из этих двух файлов образует модуль с именем crate в корне структуры модулей крейта, известной как
    дерево модулей.
    В листинге 7-2 показано дерево модулей для структуры модулей, приведённой в коде листинга 7-1.
    Листинг 7-2: Дерево модулей для для структуры модулей приведённой в коде в листинге 7-1
    Это дерево показывает, как некоторые из модулей вкладываются друг в друга; например, hosting находится внутри front_of_house
    . Дерево также показывает, что некоторые модули являются братьями (siblings) друг для друга, то есть они определены в одном модуле; hosting и serving это братья которые определены внутри front_of_house
    Если модуль A содержится внутри модуля B, мы говорим, что модуль A является
    *потомком * (child) модуля B, а модуль B является *родителем * (parent) модуля A.
    Обратите внимание, что родителем всего дерева модулей является неявный модуль с именем crate
    Дерево модулей может напомнить вам дерево каталогов файловой системы на компьютере; это очень удачное сравнение! По аналогии с каталогами в файловой системе, мы используется модули для организации кода. И так же, как нам надо искать файлы в каталогах на компьютере, нам требуется способ поиска нужных модулей.
    crate
    └── front_of_house
    ├── hosting
    │ ├── add_to_waitlist
    │ └── seat_at_table
    └── serving
    ├── take_order
    ├── serve_order
    └── take_payment

    Пути для ссылки на элемент в дереве модулей
    Чтобы показать Rust, где найти элемент в дереве модулей, мы используем путь так же,
    как мы используем путь при навигации по файловой системе. Чтобы вызвать функцию,
    нам нужно знать её путь.
    Пути бывают двух видов:
    абсолютный путь - это полный путь, начинающийся от корневого модуля крейта;
    для кода из внешнего крейта абсолютный путь начинается с имени крейта, а для кода из текущего крейта он начинается с литерала crate
    относительный путь начинается с текущего модуля и использует ключевые слова self
    , super или идентификатор в текущем модуле.
    Как абсолютные, так и относительные, пути состоят из одного или нескольких идентификаторов, разделённых двойными двоеточиями (
    ::
    ).
    Вернёмся к листингу 7-1, скажем, мы хотим вызвать функцию add_to_waitlist
    . Это то же самое, что спросить: какой путь у функции add_to_waitlist
    ? В листинге 7-3 мы немного упростили код листинга 7-1, удалив некоторые модули и функции.
    Мы покажем два способа вызова функции add_to_waitlist из новой функции eat_at_restaurant
    , определённой в корневом модуле крейта. Эти пути правильные, но остаётся ещё одна проблема, которая не позволит этому примеру скомпилироваться как есть. Мы скоро объясним почему.
    Функция eat_at_restaurant является частью общедоступного API нашего библиотечного крейта, поэтому мы помечаем её ключевым словом pub
    . В разделе "Раскрываем приватные пути с помощью ключевого слова pub
    "
    мы рассмотрим более подробно pub
    Файл: src/lib.rs
    Листинг 7-3. Вызов функции
    add_to_waitlist
    с использованием абсолютного и относительного пути
    mod front_of_house { mod hosting { fn add_to_waitlist
    () {}
    }
    } pub fn eat_at_restaurant
    () {
    // Absolute path crate::front_of_house::hosting::add_to_waitlist();
    // Relative path front_of_house::hosting::add_to_waitlist();
    }

    При первом вызове функции add_to_waitlist из eat_at_restaurant мы используем абсолютный путь. Функция add_to_waitlist определена в том же крейте, что и eat_at_restaurant
    , и это означает, что мы можем использовать ключевое слово crate в
    начале абсолютного пути. Затем мы добавляем каждый из последующих дочерних модулей, пока не составим путь до add_to_waitlist
    . Вы можете представить себе файловую систему с такой же структурой: мы указываем путь
    /front_of_house/hosting/add_to_waitlist для запуска программы add_to_waitlist
    ;
    использование имени crate в качестве корневого модуля крейта аналогично использованию
    /
    для указания корня файловой системы в вашей оболочке.
    Второй раз, когда мы вызываем add_to_waitlist из eat_at_restaurant
    , мы используем относительный путь. Путь начинается с имени модуля front_of_house
    , определённого на том же уровне дерева модулей, что и eat_at_restaurant
    . Для эквивалентной файловой системы использовался бы путь front_of_house/hosting/add_to_waitlist
    Начало пути с имени модуля означает, что путь является относительным.
    Выбор, использовать относительный или абсолютный путь, является решением, которое вы примете на основании вашего проекта. Решение должно зависеть от того, с какой вероятностью вы переместите объявление элемента отдельно от или вместе с кодом использующим этот элемент. Например, в случае перемещения модуля front_of_house и его функции eat_at_restaurant в другой модуль с именем customer_experience
    , будет необходимо обновить абсолютный путь до add_to_waitlist
    , но относительный путь всё
    равно будет действителен. Однако, если мы переместим отдельно функцию eat_at_restaurant в модуль с именем dining
    , то абсолютный путь вызова add_to_waitlist останется прежним, а относительный путь нужно будет обновить. Мы предпочитаем указывать абсолютные пути, потому что это позволяет проще перемещать определения кода и вызовы элементов независимо друг от друга.
    Давайте попробуем скомпилировать код из листинга 7-3 и выяснить, почему он ещё не компилируется. Ошибка, которую мы получаем, показана в листинге 7-4.

    1   ...   10   11   12   13   14   15   16   17   ...   62


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