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

  • Зависимость от внешних крейтов в рабочем пространстве

  • Добавление теста в рабочее пространство

  • Установка двоичных файлов с помощью

  • Расширение Cargo пользовательскими командами

  • Использование

  • Листинг 15-1: Сохранение значения i32 в куче с использованием box

  • Включение рекурсивных типов с помощью Boxes

  • Больше информации о cons списке

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


    Скачать 7.02 Mb.
    НазваниеЯзык программирования Rust
    Дата12.04.2023
    Размер7.02 Mb.
    Формат файлаpdf
    Имя файлаThe Rust Programming Language_ru.pdf
    ТипУчебник
    #1056301
    страница38 из 62
    1   ...   34   35   36   37   38   39   40   41   ...   62

    Листинг 14-7: Использование функционала библиотечного крейта
    add-one
    в крейте
    adder
    Давайте соберём рабочее пространство, запустив команду cargo build в каталоге верхнего уровня add!
    ├── Cargo.lock
    ├── Cargo.toml
    ├── add_one
    │ ├── Cargo.toml
    │ └── src
    │ └── lib.rs
    ├── adder
    │ ├── Cargo.toml
    │ └── src
    │ └── main.rs
    └── target pub fn add_one
    (x: i32
    ) -> i32
    { x +
    1
    }
    [dependencies]
    add_one = { path =
    "../add_one"
    } use add_one; fn main
    () { let num =
    10
    ; println!
    (
    "Hello, world! {num} plus one is {}!"
    , add_one::add_one(num));
    }

    Чтобы запустить бинарный крейт из каталога add, нам нужно указать какой пакет из рабочей области мы хотим использовать с помощью аргумента
    -p и названия пакета в команде cargo run
    :
    Запуск кода из adder/src/main.rs, который зависит от add_one
    Зависимость от внешних крейтов в рабочем пространстве
    Обратите внимание, что рабочая область имеет один единственный файл Cargo.lock на верхнем уровне, а не содержит Cargo.lock в каталоге каждого крейта. Это гарантирует, что все крейты используют одну и ту же версию всех зависимостей. Если мы добавим пакет rand в файлы adder/Cargo.toml и add_one/Cargo.toml, Cargo сведёт их оба к одной версии rand и запишет её в один Cargo.lock. Если заставить все крейты в рабочей области использовать одни и те же зависимости, то это будет означать, что крейты всегда будут совместимы друг с другом. Давайте добавим крейт rand в раздел
    [dependencies]
    в файле add_one/Cargo.toml, чтобы мы могли использовать крейт rand в крейте add_one
    :
    Файл: add_one/Cargo.toml
    Теперь мы можем добавить use rand;
    в файл add_one/src/lib.rs и сделать сборку рабочего пространства, запустив cargo build в каталоге add, что загрузит и скомпилирует rand крейт:
    $
    cargo build
    Compiling add_one v0.1.0 (file:///projects/add/add_one)
    Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 0.68s
    $
    cargo run -p adder
    Finished dev [unoptimized + debuginfo] target(s) in 0.0s
    Running `target/debug/adder`
    Hello, world! 10 plus one is 11!
    [dependencies]
    rand =
    "0.8.3"

    Файл Cargo.lock верхнего уровня теперь содержит информацию о зависимости add_one к
    крейту rand
    . Тем не менее, не смотря на то что rand использован где-то в рабочем пространстве, мы не можем использовать его в других крейтах рабочего пространства,
    пока не добавим крейт rand в отдельные Cargo.toml файлы. Например, если мы добавим use rand;
    в файл adder/src/main.rs крейта adder
    , то получим ошибку:
    Чтобы исправить это, отредактируйте файл Cargo.toml для пакета adder и укажите, что rand также является его зависимостью. При сборке пакета adder rand будет добавлен в список зависимостей для adder в Cargo.lock, но никаких дополнительных копий rand загружено не будет. Cargo позаботился о том, чтобы все крейты во всех пакетах рабочей области, использующих пакет rand
    , использовали одну и ту же версию, экономя нам место и гарантируя, что все крейты в рабочей области будут совместимы друг с другом.
    Добавление теста в рабочее пространство
    В качестве ещё одного улучшения давайте добавим тест функции add_one::add_one в add_one
    :
    Файл: add_one/src/lib.rs
    $
    cargo build
    Updating crates.io index
    Downloaded rand v0.8.3
    --snip--
    Compiling rand v0.8.3
    Compiling add_one v0.1.0 (file:///projects/add/add_one) warning: unused import: `rand`
    -->
    add_one/src/lib.rs:1:5
    |
    1 | use rand;
    | ^^^^
    |
    = note: `#[warn(unused_imports)]` on by default warning: 1 warning emitted
    Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished dev [unoptimized + debuginfo] target(s) in 10.18s
    $
    cargo build
    --snip--
    Compiling adder v0.1.0 (file:///projects/add/adder) error[E0432]: unresolved import `rand`
    -->
    adder/src/main.rs:2:5
    |
    2 | use rand;
    | ^^^^ no external crate `rand`

    Теперь запустите cargo test в каталоге верхнего уровня add. Запуск cargo test в
    рабочем пространстве, структурированном подобно этому, запустит тесты для всех крейтов в рабочем пространстве:
    Первая секция вывода показывает, что тест it_works в крейте add_one прошёл.
    Следующая секция показывает, что в крейте adder не было обнаружено ни одного теста,
    а последняя секция показывает, что в крейте add_one не было найдено ни одного теста документации.
    Мы также можем запустить тесты для одного конкретного крейта в рабочем пространстве из каталог верхнего уровня с помощью флага
    -p и указанием имени крейта для которого мы хотим запустить тесты:
    pub fn add_one
    (x: i32
    ) -> i32
    { x +
    1
    }
    #[cfg(test)]
    mod tests { use super::*;
    #[test]
    fn it_works
    () { assert_eq!
    (
    3
    , add_one(
    2
    ));
    }
    }
    $
    cargo test
    Compiling add_one v0.1.0 (file:///projects/add/add_one)
    Compiling adder v0.1.0 (file:///projects/add/adder)
    Finished test [unoptimized + debuginfo] target(s) in 0.27s
    Running target/debug/deps/add_one-f0253159197f7841 running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    Running target/debug/deps/adder-49979ff40686fa8e running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    Doc-tests add_one running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

    Эти выходные данные показывают, что выполнение cargo test запускает только тесты для крейта add-one и не запускает тесты крейта adder
    Если вы соберётесь опубликовать крейты из рабочего пространства на crates.io
    , каждый крейт будет необходимо будет опубликовать отдельно. Подобно cargo test
    , мы можем опубликовать конкретный крейт из нашей рабочей области, используя флаг
    -p и указав имя крейта, который мы хотим опубликовать.
    Для дополнительной практики добавьте крейт add_two в данное рабочее пространство аналогичным способом, как делали с крейт add_one
    !
    По мере роста проекта рассмотрите возможность использования рабочих областей:
    легче понять небольшие, отдельные компоненты, чем один большой кусок кода. Кроме того, хранение крейтов в рабочем пространстве может облегчить координацию между крейтами, если они часто изменяются параллельно.
    $
    cargo test
    -p add_one
    Finished test [unoptimized + debuginfo] target(s) in 0.00s
    Running target/debug/deps/add_one-b3235fea9a156f74 running 1 test test tests::it_works ... ok test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s
    Doc-tests add_one running 0 tests test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

    Установка двоичных файлов с помощью cargo install
    Команда cargo install позволяет локально устанавливать и использовать исполняемые крейты. Она не предназначена для замены системных пакетов; она используется как удобный способ Rust разработчикам устанавливать инструменты,
    которыми другие разработчики поделились на сайте crates.io
    . Заметьте, можно устанавливать только пакеты, имеющие исполняемые целевые крейты. Исполняемой
    целью (binary target) является запускаемая программа, созданная и имеющая в составе крейта файл src/main.rs или другой файл, указанный как исполняемый, в отличии от библиотечных крейтов, которые не могут запускаться сами по себе, но подходят для включения в другие программы. Обычно крейт содержит информацию в файле README,
    является ли он библиотекой, исполняемым файлом или обоими вместе.
    Все исполняемые файлы установленные командой cargo install сохранены в корневой установочной папке bin. Если вы установили Rust с помощью rustup.rs и у вас его нет в пользовательских конфигурациях, то этим каталогом будет $HOME/.cargo/bin. Он гарантирует, что каталог находится в вашем окружении
    $PATH
    , чтобы вы имели возможность запускать программы, которые вы установили командой cargo install
    Так, например, в главе 12 мы упоминали, что для поиска файлов существует реализация утилиты grep на Rust под названием ripgrep
    . Чтобы установить ripgrep
    , мы можем выполнить следующее:
    Последняя строка вывода показывает местоположение и название установленного исполняемого файла, который в случае ripgrep называется rg
    . Если вашей установочной директорией является
    $PATH
    , как уже упоминалось ранее, вы можете запустить rg --help и начать использовать более быстрый и грубый инструмент для поиска файлов!
    $
    cargo install ripgrep
    Updating crates.io index
    Downloaded ripgrep v11.0.2
    Downloaded 1 crate (243.3 KB) in 0.88s
    Installing ripgrep v11.0.2
    --snip--
    Compiling ripgrep v11.0.2
    Finished release [optimized + debuginfo] target(s) in 3m 10s
    Installing

    /.cargo/bin/rg
    Installed package `ripgrep v11.0.2` (executable `rg`)

    Расширение Cargo пользовательскими командами
    Cargo спроектирован так, что вы можете расширять его новыми субкомандами без необходимости изменения самого Cargo. Если исполняемый файл доступен через переменную окружения
    $PATH
    и назван по шаблону cargo-something
    , то его можно запускать как субкоманду Cargo cargo something
    . Пользовательские команды подобные этой также перечисляются в списке доступных через cargo --list
    . Возможность использовать cargo install для установки расширений и затем запускать их так же, как встроенные в Cargo инструменты, это очень удобное следствие продуманного дизайна
    Cargo!
    Итоги
    Совместное использование кода с Cargo и crates.io является частью того, что делает экосистему Rust полезной для множества различных задач. Стандартная библиотека Rust небольшая и стабильная, но крейты легко распространять, использовать и улучшать независимо от самого языка. Не стесняйтесь делиться кодом, который был вам полезен,
    через crates.io
    ; скорее всего, он будет полезен и кому-то ещё!

    Умные указатели
    Указатель — это общая концепция для переменной, которая содержит адрес участка памяти. Этот адрес «относится к», или «указывает на» некоторые другие данные.
    Наиболее общая разновидность указателя в Rust — это ссылка, о которой вы узнали из главы 4. Ссылки обозначаются символом
    &
    и заимствуют значение, на которое указывают. Они не имеют каких-либо специальных возможностей, кроме как ссылаться на данные, и не имеют никаких накладных расходов.
    Умные указатели, с другой стороны, являются структурами данных, которые не только действуют как указатель, но также имеют дополнительные метаданные и возможности.
    Концепция умных указателей не уникальна для Rust: умные указатели возникли в C++ и существуют в других языках. В Rust есть разные умные указатели, определённые в стандартной библиотеке, которые обеспечивают функциональность, выходящую за рамки ссылок. Одним из примеров, который мы рассмотрим в этой главе, является тип умного указателя reference counting (подсчёт ссылок). Этот указатель позволяет иметь несколько владельцев с помощью отслеживания количества владельцев и, когда владельцев не остаётся, очищает данные.
    Rust с его концепцией владения и заимствования имеет дополнительное различие между ссылками и умными указателями: в то время, как ссылки только заимствуют данные, умные указатели часто владеют данными, на которые указывают.
    Ранее мы уже сталкивались с умными указателями в этой книге, хотя и не называли их так, например
    String и
    Vec
    в главе 8. Оба этих типа считаются умными указателями,
    потому что они владеют некоторой областью памяти и позволяют ею манипулировать. У
    них также есть метаданные и дополнительные возможности или гарантии.
    String
    ,
    например, хранит свой размер в виде метаданных и гарантирует, что содержимое строки всегда будет в кодировке UTF-8.
    Умные указатели обычно реализуются с помощью структур. Характерной чертой, которая отличает умный указатель от обычной структуры, является то, что для умных указателей реализованы типажи
    Deref и
    Drop
    . Типаж
    Deref позволяет экземпляру умного указателя вести себя как ссылка, так что вы можете написать код, работающий с ним как со ссылкой, так и как с умным указателем. Типаж
    Drop позволяет написать код, который будет запускаться когда экземпляр умного указателя выйдет из области видимости. В
    этой главе мы обсудим оба типажа и продемонстрируем, почему они важны для умных указателей.
    Учитывая, что паттерн умного указателя является общим паттерном проектирования,
    часто используемым в Rust, эта глава не описывает все существующие умные указатели.
    Множество библиотек имеют свои умные указатели, и вы также можете написать свои.
    Мы охватим наиболее распространённые умные указатели из стандартной библиотеки:
    Box
    для распределения значений в куче (памяти)

    Rc
    тип счётчика ссылок, который допускает множественное владение
    Типы
    Ref
    и
    RefMut
    , доступ к которым осуществляется через тип
    RefCell
    ,
    который обеспечивает правила заимствования во время выполнения вместо времени компиляции
    Дополнительно мы рассмотрим паттерн внутренней изменчивости (interior mutability), где неизменяемый тип предоставляет API для изменения своего внутреннего значения. Мы также обсудим ссылочные зацикленности (reference cycles): как они могут приводить к утечке памяти и как это предотвратить.
    Приступим!

    Использование Box для ссылки на данные в куче
    Наиболее простой умный указатель - это box, чей тип записывается как
    Box
    . Такие переменные позволяют хранить данные в куче, а не в стеке. То, что остаётся в стеке,
    является указателем на данные в куче. Обратитесь к Главе 4, чтобы рассмотреть разницу между стеком и кучей.
    У Box нет проблем с производительностью, кроме хранения данных в куче вместо стека.
    Но он также и не имеет множества дополнительных возможностей. Вы будете использовать его чаще всего в следующих ситуациях:
    Когда у вас есть тип, размер которого невозможно определить во время компиляции, а вы хотите использовать значение этого типа в контексте, требующем точного размера.
    Когда у вас есть большой объем данных и вы хотите передать владение, но при этом быть уверенным, что данные не будут скопированы
    Когда вы хотите получить значение во владение и вас интересует только то, что оно относится к типу, реализующему определённый трейт, а не то, является ли оно значением какого-то конкретного типа
    Мы продемонстрируем первую ситуацию в разделе "Реализация рекурсивных типов с помощью Box"
    . Во втором случае, передача владения на большой объем данных может занять много времени, потому что данные копируются через стек. Для повышения производительности в этой ситуации, мы можем хранить большое количество данных в куче с помощью Box. Затем только небольшое количество данных указателя копируется в стеке, в то время как данные, на которые он ссылается, остаются в одном месте кучи.
    Третий случай известен как типаж объект (trait object) и глава 17 посвящает целый раздел "Использование типаж объектов, которые допускают значения разных типов"
    только этой теме. Итак, то, что вы узнаете здесь, вы примените снова в Главе 17!
    Использование Box для хранения данных в куче
    Прежде чем мы обсудим этот вариант использования
    Box
    , мы рассмотрим синтаксис и то, как взаимодействовать со значениями, хранящимися в
    Box
    В листинге 15-1 показано, как использовать поле для хранения значения i32
    в куче:
    Файл: src/main.rs
    Листинг 15-1: Сохранение значения
    i32
    в куче с использованием box
    fn main
    () { let b =
    Box
    ::new(
    5
    ); println!
    (
    "b = {}"
    , b);
    }

    Мы объявляем переменную b
    со значением
    Box
    , указывающим на число
    5
    ,
    размещённое в куче. Эта программа выведет b = 5
    ; в этом случае мы получаем доступ к данным в box так же, как если бы эти данные находились в стеке. Как и любое другое значение, когда box выйдет из области видимости, как b
    в конце main
    , он будет удалён.
    Деаллокация происходит как для box ( хранящегося в стеке), так и для данных, на которые он указывает (хранящихся в куче).
    Размещать одиночные значения в куче не слишком целесообразно, поэтому вряд ли вы будете часто использовать box'ы таким образом. В большинстве ситуаций более уместно размещать такие значения, как i32
    , в стеке, где они и сохраняются по умолчанию.
    Давайте рассмотрим ситуацию, когда box позволяет нам определить типы, которые мы не могли бы иметь, если бы у нас не было box.
    Включение рекурсивных типов с помощью Boxes
    Значение рекурсивного типа может иметь другое значение такого же типа как свой компонент. Рекурсивные типы представляют собой проблему, поскольку во время компиляции Rust должен знать, сколько места занимает тип. Однако вложенность значений рекурсивных типов теоретически может продолжаться бесконечно, поэтому
    Rust не может определить, сколько места потребуется. Поскольку box имеет известный размер, мы можем включить рекурсивные типы, добавив box в определение рекурсивного типа.
    В качестве примера рекурсивного типа рассмотрим cons list. Это тип данных, часто встречающийся в функциональных языках программирования. Тип cons list, который мы определим, достаточно прост, за исключением наличия рекурсии; поэтому концепции,
    заложенные в примере, с которым мы будем работать, пригодятся вам в любой более сложной ситуации, связанной с рекурсивными типами.
    Больше информации о cons списке
    cons list - это структура данных из языка программирования Lisp и его диалектов,
    представляющая собой набор вложенных пар и являющаяся Lisp-версией связного списка. Его название происходит от функции cons
    (сокращение от "construct function") в
    Lisp, которая формирует пару из двух своих аргументов. Вызывая cons для пары, которая состоит из некоторого значения и другой пары, мы можем конструировать списки cons,
    состоящие из рекурсивных пар.
    Вот, пример cons list написанный на псевдокоде, содержащий список 1, 2, 3 где каждая пара заключена в круглые скобки:
    (1, (2, (3, Nil)))

    Каждый элемент в cons списке содержит два элемента: значение текущего элемента и следующий элемент. Последний элемент в списке содержит только значение называемое
    Nil без следующего элемента. Cons список создаётся путём рекурсивного вызова функции cons
    . Каноничное имя для обозначения базового случая рекурсии -
    Nil
    Обратите внимание, что это не то же самое, что понятие “null” или “nil” из главы 6,
    которая является недействительным или отсутствующим значением.
    Сons list не является часто используемой структурой данных в Rust. В большинстве случаев, когда вам нужен список элементов при использовании Rust, лучше использовать
    Vec
    . Другие, более сложные рекурсивные типы данных полезны в определённых ситуациях, но благодаря тому, что в этой главе мы начнём с cons list, мы сможем выяснить, как box позволяет нам определить рекурсивный тип данных без особого напряжения.
    Листинг 15-2 содержит объявление перечисления cons списка. Обратите внимание, что этот код не будет компилироваться, потому что тип
    List не имеет известного размера,
    что мы и продемонстрируем.
    Файл: src/main.rs
    1   ...   34   35   36   37   38   39   40   41   ...   62


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